Dynamic
<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->
<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->
Dynamic — what you can do
/admin/d/dynamic · pillar dynamic
The operator's own layer - code, machinery, registries, ways in and out, and what they publish.
Fetch this, never remember it
These change per install and per release. Call for them - a list written here would be a future lie.
| What | Call | Why |
|---|---|---|
| every trigger type this install can arm, and every action it can run | GET /api/automation/triggers | both catalogues are assembled from what is registered on THIS deployment, and client-runtime entries appear beside server ones |
| whether a rule is actually armed, and why not | GET /api/automation/rules/status | armed, disabled, failed-to-arm and never-seen are four different answers and only this endpoint tells them apart |
| every job kind this install can run | GET /api/jobs/kinds | a kind carries its engine, its concurrency, whether it can be scheduled and whether the fleet can take it - all deployment facts |
| which script interpreters exist here | GET /api/dynamic/scripts/languages | the sandboxed set is always present; the native ones depend on the machine and on a flag, so the answer differs per install |
| everything callable, with its declared inputs | GET /api/function-index/entries | roughly twenty thousand entries assembled from the tree and four registries - never guess a function name, search it |
| what may be bound to a webhook | GET /api/webhooks/bindable | a refused binding comes back with its reason rather than being hidden, so this is also the answer to "why can I not point at that" |
How to actually do things here
Written by hand, because the order and the traps are the part no file can derive.
Make something happen when something else happens
GET /api/automation/triggersfor what can fire here,GET /api/automation/actionsfor what can run.- Write the rule as a row:
POST /db/ruleswith{name, enabled, trigger:{type,props}, actions:[{type,props}]}. - Prove it before trusting it:
POST /api/automation/simulate/:triggerIdpushes a synthetic payload through the rule's own actions. A handful of types can be fired for real withPOST /api/automation/fire/:triggerId. GET /api/automation/rules/statusto confirm it armed. Watch it live onGET /api/automation/stream.
Trap: [critical] THE COMMONEST SILENT FAILURE IS A MISSING REQUIRED TRIGGER PROP — the rule arms, looks healthy and never fires. status reports error (why it would not arm) and lastError (why its last run failed) as INDEPENDENT fields, and null means never seen, which is different again. There is also a rule-skipped event for a rule whose trigger fired but whose own condition did not match; without it "my rule never fires" has no answer.
Build a workflow rather than a rule
- A workflow is a DOCUMENT:
POST /db/workflows. Its editor is the one surface here with live tools. get_workflow_overview, thenlist_nodesandlist_edges— never edit a graph you have not read.list_blocksnames every node type;describe_blockgives one its contract before you place it.add_node,set_node_action,set_node_executor,set_node_props, thenconnect_nodes.list_data_sourcesBEFORE writing any{{steps...}}token — it grades each source as having actually run, merely being declared, or being structural.step_workflowproves one step.save_workflowis the only tool that persists anything.
Trap: [critical] A NODE NAME WITH A SPACE CANNOT BE ADDRESSED IN A TOKEN, and an unresolvable token silently becomes an empty string while the run still reports success. Name nodes like identifiers. step_workflow runs what is SAVED, not the draft. Running the whole graph is deliberately not a tool — it is the operator's gesture.
Give somebody outside an address to POST to
GET /api/webhooks/bindablefor what may be pointed at; a refusal comes with its reason.POST /api/webhookswith{name, auth, binding:{kind,ref,config}, argMap, response}. The address is returned ONCE, on create — that is the only moment the author has not yet had a chance to write it down.- Map the incoming body deliberately:
argMapis a whitelist of{input, from}, never a spread of whatever arrived. - Watch
GET /api/webhooks/deliveries— it is the only place a refusal explains itself. POST /api/webhooks/:id/rotatemints a new address and kills the old one immediately.
Trap: [critical] EVERY REFUSAL LOOKS IDENTICAL from outside — unknown id, disabled row, bad signature and stale timestamp all answer the same way, because distinguishing them would turn the endpoint into an oracle for guessing addresses. With auth.mode: "none" the address IS the credential. A signing secret lives in a variable, never on the row. Some actions cannot be bound at all — an anonymous caller reaching an outbound HTTP action would be a proxy wearing this API's network position.
Add a collection the operator can fill in
GET /api/dynamic/schemas/collectionsfirst — it says which names are taken or reserved.- Have the shape inferred rather than typed:
POST /api/dynamic/schemas/inferwith sample data. POST /api/dynamic/schemaswith{name, collection, schema, validate, history}.- Try it before committing anyone to it:
POST /api/dynamic/schemas/:id/validatedry-runs a document, andGET /api/dynamic/schemas/:id/mockgenerates rows. - Rows then live at
/db/:collectionlike everything else.
Trap: [critical] validateInputs RETURNS AN OBJECT, {ok, errors} — truth-testing the return value passes for every input, valid or not. A type is enforced only if it was DECLARED, and an undeclared key is never an error. A collection name is locked after creation. Deleting the model keeps the rows.
Run a script
GET /api/dynamic/scripts/languages— the sandboxed interpreters are always there; node, python and shell depend on this machine and a flag.POST /api/dynamic/scripts/runruns an unsaved draft;POST /api/dynamic/scripts/:id/runruns a saved one.- Arguments are typed: declare them as inputs and they generate their own form.
Trap: PRIVILEGE IS A PROPERTY OF THE RUN, NOT THE ROW — a member may author and save a native script on an instance that will never execute it, and the refusal names the first failing condition only. For writing the scripts themselves, the leumas-lmx skill is the authority; this lane owns only the library and the native escape hatch.
Find out what can be called at all
GET /api/function-index/entrieswith a search — it is the catalogue half of the one dispatch descriptor every callable thing in Leumas shares.GET /api/function-index/entry/:idfor one;POST /api/function-index/entry/:id/deepre-parses it properly when the fast pass was not enough.- A useful entry can be PROMOTED into a real action, after which a rule or a workflow can point at it.
GET /api/function-index/scansanswers the question the index exists for: which promoted actions now point at something that moved or is gone.
Trap: A rescan MERGES rather than replaces, so what the operator marked — allowed, promoted, pinned — survives. The diff is scoped to one source, so rescanning a project cannot disturb another's rows.
Tabs
Every one is a real URL. A tab with its own section below carries a real job.
| Tab | Path | Group | What it is for |
|---|---|---|---|
(index) | - | - | Register a file on this machine as something Leumas will render and serve. |
catalog | - | - | Every component the Leumas packages themselves export — read-only, and much larger than it looks. |
consume | - | - | The copy-paste snippets for using one of your components — a script tag, a React import, a curl, a module import. |
(index) | - | - | Mount your own API, a local server, a static folder or a proxy at a path on this Studio. |
startup | - | - | What mounted at boot, what was skipped and what failed — with the reason on each row. |
(index) | - | - | Write, save and run a script with typed arguments. |
languages | - | - | Which interpreters this machine can actually run, sandboxed and native, and why a native one is unavailable. |
(index) | - | - | Declare the shape of a collection, and get a form, validation and history from it. |
records | - | - | The rows themselves, in a form generated from the model. |
(index) | - | - | Every input control this Studio can draw, live, so you can see one before you declare it. |
themes | - | - | How every generated form looks — a record of design tokens, not a stylesheet. |
(index) | - | - | The rules: when this fires, run that. |
types | - | - | Author a narrower trigger from an existing one — the same event, with a condition in front of it. |
feed | - | - | Rules firing live — the fastest way to tell a rule that never armed from one that armed and did nothing. |
catalog | - | - | Every trigger type registered on this install, with the props each needs. |
(index) | - | - | Two stored kinds in one list: reusable presets, and action TYPES that are themselves registered actions. |
catalog | - | - | Every registered action manifest, with a test run. |
workflows | /admin/dynamic/workflows | - | Graphs: branches, loops, waits, parallel legs, human approval and sub-workflows. |
macros | /admin/dynamic/macros | - | Record and replay a sequence of real mouse and keyboard input on the machine running the API. |
(index) | - | - | The queue — what is running, what is waiting, and what is blocking it. |
history | - | - | Finished work, with what each run produced or why it failed. |
submit | - | - | Hand work in by hand — which is how a kind gets proven before a rule depends on it. |
schedules | - | - | Plans that place work on a cadence — a cron, an interval, or once at a time. |
kinds | - | - | Every kind of work this install knows how to run, and where each can run. |
(index) | - | - | Every KIND of thing this Studio knows about, merged from several separate registries into one list. |
guide | - | - | A wizard that walks you through adding one — and for the two kinds that cannot be added at run time, hands you the snippet and names the file. |
(index) | - | - | Search everything callable in this install, with the arguments each one declares. |
sources | - | - | Which trees are scanned, and when each was last read. |
scans | - | - | Each scan and what changed — new, moved, and gone. |
adapters | /admin/dynamic/adapters | - | Every adapter system loaded here, every function on it, and a form to run one. |
iframes | /admin/dynamic/iframes | - | Every embeddable page this instance can open — a shipped catalogue of games and tools, plus any an operator adds — as a filtered grid of cover art; each opens on its own route. |
(index) | - | - | Inbound addresses: somebody outside POSTs, and a thing you chose runs. |
deliveries | - | - | Every inbound delivery and every refusal, with the reason — the ONLY place a refusal explains itself. |
outbound | - | - | Subscriptions that would POST outward when something happens here. |
(index) | - | - | One value, set once, read by everything — rules, workflows, scripts and webhook secrets. |
usage | - | - | How to reference a variable, and the one place it is deliberately NOT expanded. |
(index) | - | - | The operator's public link page — the one thing here an anonymous visitor can already see. |
appearance | - | - | The link page's own art direction — name, bio, theme, layout, button shape and social icons. |
insights | - | - | Clicks over the last month, per day. |
(index) | - | - | Browse and tune every public station — radio and TV. |
my-stations | - | - | Host your own station: sources, uploads, cover art, and whether it is a playlist or a live broadcast. |
registry | - | - | Search the world's public radio registry, and contribute a station to it. |
globe | - | - | Radio, TV, webcams and live data on one globe — a way to FIND a station by place. |
broadcast | - | - | The transmitter room — start and stop a real live broadcast for a station. |
providers | - | - | Which upstream sources are configured, what each needs, and a live test. |
analytics | - | - | Your stations ranked by plays and reactions, plus the health of the world feeds. |
monitor | - | - | Live flights, ships, satellites, quakes, fires and news on one globe. |
Tabs in focus
(index) - Components
Register a file on this machine as something Leumas will render and serve.
Read with: GET /api/dynamic/ssr | GET /api/dynamic/ssr/:key
Write with: POST /api/dynamic/ssr | PATCH /api/dynamic/ssr/:key | POST /api/dynamic/ssr/:key/rebuild
- Register the path, the export and the props it takes.
- Preview it with the SAME render call a consumer makes —
POST /api/dynamic/ssr/:key/render. - Make it
publicbefore anything outside Studio can reach it.
Trap: Only a component KIND renders; the other kinds are served as files. Every path is checked against the allowed roots at every use, not once at save, so a row restored from a backup is re-checked. Imports are an allow-list — an unexpected bare import is a build error rather than a silent undefined.
catalog - Catalog
Every component the Leumas packages themselves export — read-only, and much larger than it looks.
Trap: This is the generated ecosystem catalogue, not the operator's own components. It is a reference, not a registry you can add to.
consume - Consume
The copy-paste snippets for using one of your components — a script tag, a React import, a curl, a module import.
Trap: The snippets are built from what the API reports, so a private component is flagged as unreachable rather than quietly producing a snippet that would 404.
(index) - Registry
Mount your own API, a local server, a static folder or a proxy at a path on this Studio.
Read with: GET /api/dynamic/routers | GET /api/dynamic/routers/kinds | GET /api/dynamic/routers/templates
Write with: POST /api/dynamic/routers | POST /api/dynamic/routers/:id/remount | POST /api/dynamic/routers/reload
GET /api/dynamic/routers/kinds— the form for every kind is generated from it, so it is also the field list.POST /api/dynamic/routers/validatedry-runs an unsaved config, imports included.- Save, then remount. A server kind additionally has start, stop and restart.
Trap: [critical] A REMOUNT CANNOT UNDO WHAT A MODULE DID WHEN IT WAS IMPORTED. Mounts are also refused on any path Leumas reserves, and dynamic mounts are registered AFTER the product's own routes so a user mount can never win a path Leumas owns.
startup - Startup report
What mounted at boot, what was skipped and what failed — with the reason on each row.
Read with: GET /api/dynamic/routers/startup-report
Trap: Three outcomes, and they are different: skipped means disabled or not set to load at startup; failed carries the error; ready may add that it resolves per request. A router that silently never loaded shows up here and nowhere else.
(index) - Library
Write, save and run a script with typed arguments.
Read with: GET /api/dynamic/scripts | GET /api/dynamic/scripts/languages
Write with: POST /api/dynamic/scripts | POST /api/dynamic/scripts/run | POST /api/dynamic/scripts/:id/run
Trap: An unsaved draft still runs — the run route takes the source directly. Declared arguments generate their own form, so declaring them is what makes a script usable by anyone but its author.
languages - Languages
Which interpreters this machine can actually run, sandboxed and native, and why a native one is unavailable.
Trap: The native lane reports the FIRST failing condition only — not on this deployment, then not enabled, then not an admin. Fixing one may reveal the next.
(index) - Library
Declare the shape of a collection, and get a form, validation and history from it.
Read with: GET /api/dynamic/schemas | GET /api/dynamic/schemas/collections | GET /api/dynamic/schemas/starters
Write with: POST /api/dynamic/schemas/infer | POST /api/dynamic/schemas | PATCH /api/dynamic/schemas/:id
- Check the name is free, infer the shape from sample data, then save.
POST /api/dynamic/schemas/:id/validatedry-runs a document without writing it.- Turn on enforcement only when the shape has settled — a refused write is a 422 the operator has to understand.
Trap: The stored truth is plain JSON Schema; presentation rides on the same document rather than in a sidecar. A model can also be EXPOSED as an adapter, which turns its collection into callable list/get/create/update/remove functions in one step.
records - Records
The rows themselves, in a form generated from the model.
Write with: POST /db/:collection | PUT /db/:collection/:id | DELETE /db/:collection/:id
Trap: Writes go through the universal CRUD lane, so ownership, validation, history and undo are its behaviour rather than this surface's.
(index) - Deck
Every input control this Studio can draw, live, so you can see one before you declare it.
Trap: A reference deck, not a registry — you cannot add one here. It is paginated because each control is a real live component, not a picture.
themes - Themes
How every generated form looks — a record of design tokens, not a stylesheet.
Write with: PUT /db/settings/:id
Trap: A theme is data, so changing it needs no build and no restart. It is stored with the settings, never with a site config.
(index) - Rules
The rules: when this fires, run that.
Read with: GET /api/automation/rules/status | GET /api/automation/triggers
Write with: POST /db/rules | POST /api/automation/simulate/:triggerId | POST /api/automation/fire/:triggerId | POST /api/automation/rearm
- Read the status before changing anything — armed, disabled, failed and never-seen look identical on the row itself.
- Write the rule, then SIMULATE it. Every rule is testable; most trigger types cannot be fired for real.
Trap: One rule that cannot arm must not take the others with it — each arm is wrapped, and a failure is reported rather than thrown. If a rule looks armed and never fires, suspect a required trigger prop that was never set.
types - Types
Author a narrower trigger from an existing one — the same event, with a condition in front of it.
Trap: [critical] THE FILTER IS DATA, NOT AN EXPRESSION — no code, and deliberately no regular expressions, because this collection is writable by any signed-in user and a hostile pattern would stall the API for every event. Filter against the fields the trigger actually emits: a condition on a field that is not in the payload matches nothing and fails silently.
feed - Live feed
Rules firing live — the fastest way to tell a rule that never armed from one that armed and did nothing.
Read with: GET /api/automation/stream
(index) - Library
Two stored kinds in one list: reusable presets, and action TYPES that are themselves registered actions.
Read with: GET /api/automation/actions/status
Write with: POST /db/actions | POST /db/action_types
Trap: A preset is a saved set of props and a convenience. An action type is bound through an executor and IS callable. Anything promoted from the function index arrives here marked as such.
catalog - Catalog
Every registered action manifest, with a test run.
Write with: POST /api/automation/run-action
Trap: Some actions run in the browser rather than on the server, and the surface says which — a client-runtime action cannot be run by a rule on the server.
workflows - Workflows
Graphs: branches, loops, waits, parallel legs, human approval and sub-workflows.
Address: /admin/dynamic/workflows
Read with: get_workflow_overview | list_nodes | list_edges | list_blocks | describe_block | list_data_sources | read_last_trace
Write with: add_node | remove_node | set_node | set_node_action | set_node_executor | set_node_props | connect_nodes | disconnect | set_workflow_meta | auto_layout | step_workflow | save_workflow
- Read the graph first. Then edit the draft. Then
save_workflow— nothing before it persists. step_workflowruns one node of the SAVED graph, which is how a change is proven before it is trusted.
Trap: A workflow fails QUIETLY: a node nobody wired, a branch with one leg, a renamed action id — none throw, the run takes a short path and reports completed with half the work missing. That is why the gallery validates every card with the same function the API refuses a graph with.
macros - Macros
Record and replay a sequence of real mouse and keyboard input on the machine running the API.
Address: /admin/dynamic/macros
Read with: GET /api/macros/ | GET /api/macros/health
Write with: POST /api/macros/record/start | POST /api/macros/record/stop | POST /api/macros/:id/play | POST /api/macros/stop-all
Trap: Desktop-only and admin to play. It drives the real cursor, so a replay takes the machine over — know the stop hotkey before starting one, and never start a replay the operator did not ask for.
(index) - Queue
The queue — what is running, what is waiting, and what is blocking it.
Read with: GET /api/jobs/ | GET /api/jobs/stats
Write with: POST /api/jobs/:id/cancel
Trap: A job nobody can run yet is WAITING with a reason, not failed — turning the right machine on drains it. Read the reason before reporting a problem.
history - History
Finished work, with what each run produced or why it failed.
Read with: GET /api/jobs/ | GET /api/jobs/:id/events
Write with: POST /api/jobs/:id/retry
Trap: A retry is a NEW job, not a resumed one.
submit - Submit
Hand work in by hand — which is how a kind gets proven before a rule depends on it.
Read with: GET /api/jobs/kinds
Write with: POST /api/jobs/
Trap: Submitting is authorized PER KIND and denies by default, so a kind an admin can submit may be refused for a member. The kind also says whether it can be handed to another machine or only runs here.
schedules - Schedules
Plans that place work on a cadence — a cron, an interval, or once at a time.
Read with: GET /api/planner/plans | GET /api/planner/occurrences
Write with: POST /api/planner/plans | POST /api/planner/plans/:id/run
(index) - Index
Every KIND of thing this Studio knows about, merged from several separate registries into one list.
Read with: GET /api/room/kinds | GET /api/dynamic/schemas
Trap: The word "entity" names more than one unrelated registry here: a species that appears in the 3D room, a picker type that makes an id field render a dropdown of real things, and a collection shape the operator wrote. They are declared in different places and only one of them can be created at run time. A family whose registry is absent on this install reports itself unavailable rather than empty.
guide - Add an entity
A wizard that walks you through adding one — and for the two kinds that cannot be added at run time, hands you the snippet and names the file.
Write with: POST /api/dynamic/schemas
Trap: Only the operator-authored model can actually be created from here. Pretending all of them could be would be a button that lies, so the other two produce code to paste instead.
(index) - Browse
Search everything callable in this install, with the arguments each one declares.
Read with: GET /api/function-index/entries | GET /api/function-index/entry/:id | GET /api/function-index/kinds
Write with: POST /api/function-index/entry/:id/deep | POST /api/function-index/promote/:id | POST /api/function-index/invoke/:id
- Search rather than guess — the index is tens of thousands of entries.
- Read where an argument's type came from; a type inferred from a default is weaker than one declared in metadata.
- Promote an entry to turn it into a real action a rule or workflow can point at.
Trap: This is the CATALOGUE half of the one dispatch descriptor every callable thing shares — what can be called, not permission to call it. Every consumer gates separately.
sources - Sources
Which trees are scanned, and when each was last read.
Read with: GET /api/function-index/sources
Write with: POST /api/function-index/scan
Trap: On a hosted install there are no trees to scan and this tab is legitimately empty — while Browse still works, because most of what it indexes is a fact about the running instance rather than about a disk. Registering a project in the coding agent adds a source here.
scans - Scans
Each scan and what changed — new, moved, and gone.
Read with: GET /api/function-index/scans
Trap: This exists to answer one question: which promoted actions now point at a function that moved or no longer exists.
adapters - Adapters
Every adapter system loaded here, every function on it, and a form to run one.
Address: /admin/dynamic/adapters
Read with: GET /api/adapters | GET /api/adapters/tools | GET /api/adapters/:system
Write with: POST /api/adapters/:system/:fn
Trap: READ AND RUN ONLY — there is no create-an-adapter form here. An operator gets their own adapter by publishing an app: an app's actions are registered into this same registry, which is what makes them callable as tools, as MCP tools and over HTTP without writing a route.
iframes - Iframes
Every embeddable page this instance can open — a shipped catalogue of games and tools, plus any an operator adds — as a filtered grid of cover art; each opens on its own route.
Address: /admin/dynamic/iframes
Read with: GET /db/:collection | GET /db/:collection/:id
Write with: POST /db/:collection | PUT /db/:collection/:id | DELETE /db/:collection/:id
- Narrow with search or the Kind / Provider / Category facets. The narrowing is in the URL, so a filtered list is linkable.
- Open one:
/admin/dynamic/iframes/<id>is a real route — refreshable, bookmarkable, reachable from ⌘K. - Add your own with the header button, or Duplicate a built-in to get an editable copy you own.
Trap: Each row carries a thumb — cover art hotlinked from the provider CDN for a game, or /embeds/<id>.webp, a screenshot of the tool taken at build time. A hotlinked cover can rot, so the card stacks it over a glyph and hides it on error. Plain CRUD on the iframes collection — no bespoke routes. Built-in rows are seeded with seed: true and NO owner, and canWrite is isOwner, so only an admin can edit or delete one; anyone else duplicates it instead. Kind and Provider are facts, Category is a keyword guess and about a third of the games are honestly Uncategorised. Every catalogue URL was probed at build time for liveness, framing permission AND that it serves what the tile claims (a portal will substitute another game for a delisted slug rather than 404), which is why only 500 of the 1,233 source URLs survive — a provider sending X-Frame-Options can never be framed, and Chrome fires load on a blocked frame, so this cannot be detected at runtime.
(index) - Endpoints
Inbound addresses: somebody outside POSTs, and a thing you chose runs.
Read with: GET /api/webhooks/ | GET /api/webhooks/bindable | GET /api/webhooks/kinds
Write with: POST /api/webhooks/ | PATCH /api/webhooks/:id | POST /api/webhooks/:id/reveal | POST /api/webhooks/:id/rotate
- Choose what it runs from the bindable list — a refusal comes with its reason attached.
- Map the body deliberately; the mapping is a whitelist, not a spread.
- Copy the address on create. Reveal it later if you must; rotate it if it leaked.
Trap: Unsigned means the address itself is the only secret, and the surface says so. A signing secret is stored as a variable at instance scope, never on the webhook row.
deliveries - Deliveries
Every inbound delivery and every refusal, with the reason — the ONLY place a refusal explains itself.
Read with: GET /api/webhooks/deliveries
Trap: The endpoint itself answers every failure identically on purpose, so this log is the only diagnosis available. It is kept for a month, because the question is usually about last week.
outbound - Outbound
Subscriptions that would POST outward when something happens here.
Trap: [critical] NOT BUILT. The tab renders a notice saying so. Inbound is what exists today — do not promise an operator outbound delivery from this surface.
(index) - Values
One value, set once, read by everything — rules, workflows, scripts and webhook secrets.
Read with: GET /api/variables/ | GET /api/variables/kinds
Write with: POST /api/variables/ | PUT /api/variables/:key | POST /api/variables/:key/reveal
Trap: A secret's value is OMITTED by the API, not masked — reveal is a separate, deliberate call. Sealing is three states, not two: on, off, and unknown; a row marked secret on a deployment with no sealing key is stored in plain text and the surface says so in red. Publishing a variable externally makes it readable by any API key with the scope, and a secret can never be published that way.
usage - Usage
How to reference a variable, and the one place it is deliberately NOT expanded.
Trap: [critical] A VARIABLE IS EXPANDED ONLY IN AUTHORED PROPS — a rule's action, a workflow node, a webhook secret. It is NEVER expanded in data that arrived from outside, or a sender could put a reference to your secret in their request body and have this instance resolve it for them.
(index) - Links
The operator's public link page — the one thing here an anonymous visitor can already see.
Read with: GET /social/profiles/me
Write with: PUT /social/profiles/me
Trap: It edits the same payload the dashboard does, so there is one source of truth and two doors. The page is live the moment the profile has a handle.
appearance - Appearance
The link page's own art direction — name, bio, theme, layout, button shape and social icons.
Trap: This is the LINK PAGE's look, not a website's. A hosted site's colours and fonts are the site editor's appearance tab, and the two are different products.
insights - Insights
Clicks over the last month, per day.
Read with: GET /api/link-stats/
Trap: Counting started when the feature shipped and does not reach back over earlier visits, so a zero on a live page is not necessarily wrong. The stats route exists because the underlying rows are readable by any signed-in user, so the owner scoping has to happen on the server.
(index) - Stations
Browse and tune every public station — radio and TV.
Read with: GET /api/media/stations/public | GET /api/media/stations/:id
Trap: Reading a station somebody published is listening, not operating, so this half is open to any signed-in user. Creating and hosting one is not.
my-stations - My Station
Host your own station: sources, uploads, cover art, and whether it is a playlist or a live broadcast.
Read with: GET /api/media/stations
Write with: POST /api/media/stations | PUT /api/media/stations/:id | POST /api/media/stations/:id/sources | POST /api/media/stations/:id/upload
Trap: The station is DATA. A synced playlist needs no encoding at all — every listener's browser plays the same ordered sources and seeks to a shared position — which is why it scales for free and live broadcasting does not.
registry - Registry
Search the world's public radio registry, and contribute a station to it.
Read with: GET /api/media/radio/registry
Write with: POST /api/media/radio/registry
Trap: Searching is free; contributing is not — the plan pays for what you publish, not for what you look at.
globe - Live Globe
Radio, TV, webcams and live data on one globe — a way to FIND a station by place.
Read with: GET /api/media/radio/nearby | GET /api/media/providers/:plugin
Trap: Two nearby-style lookups exist and their secure-only defaults are OPPOSITE, so a station that appears in one and not the other is a filter, not a fault.
broadcast - Go Live
The transmitter room — start and stop a real live broadcast for a station.
Write with: POST /api/media/stations/:id/broadcast/start | POST /api/media/stations/:id/broadcast/stop
Trap: Live broadcasting runs an encoder on the server and is heavy and stateful, so it is opt-in and available only where the deployment was set up for it. A synced playlist is the default and needs none of it.
providers - Providers
Which upstream sources are configured, what each needs, and a live test.
Read with: GET /api/media/providers/:plugin
Trap: Keys are read on the server and never sent to the browser — this panel reports only whether one is present. A test reports the provider's own verdict, including not-configured, which is different from offline.
analytics - Analytics
Your stations ranked by plays and reactions, plus the health of the world feeds.
Read with: GET /api/media/stations/:id/stats
monitor - World Monitor
Live flights, ships, satellites, quakes, fires and news on one globe.
Read with: GET /api/media/world/traffic/live | GET /api/media/world/events/live | GET /api/media/world/layers
Trap: This is the SAME surface the Information domain mounts as its World tab — one component, two doors, and a fix lands on both. The only difference is the gate: here it sits behind the media entitlement.
Ways in
Declared cross-links. Every address is matched against the real route table by check:huds.
| From | Does | Go to |
|---|---|---|
dynamic/macros | Run it as a job | /admin/dynamic/jobs/submit |
Tools an agent can call here
These are live page capabilities: they exist only while that surface is open, and they call the page's own mutators, so a change made through one is a change the operator can see. A tool missing from this list is not callable — reach for the API or an adapter instead.
ai:workflow-editor — Workflow: …
Source: dynamic/model/useWorkflowCapabilities.js
What the page tells an agent about itself:
The user is editing a WORKFLOW — a graph of nodes wired together. Control flow is the wires: an edge leaves a specific OUTPUT HANDLE, which is how a branch is a branch ("true" and "false" are different wires, not a property). Data flow is separate: a node reads an earlier one through a {{steps.<stepKey>.output.x}} token in its arguments. Always call get_workflow_overview before reasoning about or changing the workflow — it carries the node-type vocabulary, the legal handle ids, and everything the validator has found. TWO LANES, do not confuse them: every add/remove/set/connect tool changes the DRAFT and persists nothing; save_workflow is the only tool that writes. Make all your edits, then save once at the end. Never report work as done before save_workflow has returned. NAME NODES LIKE IDENTIFIERS — fetch_order, not "Fetch order". A step key takes letters, numbers, _ and $ only, so a label with a space in it cannot be addressed in a token at all, and a token that cannot resolve silently becomes an empty string while the run still reports success. list_nodes tells you each node's real stepKey. BEFORE you write arguments that read an earlier step, call list_data_sources for that node. It returns the tokens that are actually legal there and says how certain each one is: "ran" means a node produced exactly that last run, "declared" means something claims it, "structural" means it is always true. Do not invent a path. BEFORE you bind a block, call list_blocks to find it and describe_block to read its arguments. Never invent a ref or an argument name — a node bound to something that does not exist, or called with a key it does not take, runs and does nothing. Almost nothing here declares what it RETURNS. When you need to know, use step_workflow: it advances one node and hands you the real output, which you can then wire from. Save first — it runs what is saved, not the draft. You cannot run the whole workflow, and that is deliberate: a workflow node calls real things — endpoints that write, actions that send mail, integrations that touch a customer's connected accounts. Build it, step through it to check the shapes, save it, and ask the operator to press Run. A node you add is not wired to anything unless you say after. An unwired node never runs, and the validator only warns about it — it will not stop a save. The editor is a heads-up display: the graph is drawn full-screen behind panels the user can close. Use select_node, open_workflow_panel and fit_view to put what you are discussing in front of them, and call auto_layout after building so the result is readable rather than a pile.
| Tool | Args | Does | |
|---|---|---|---|
get_workflow_overview | — | Read the open workflow: name, description, whether it is enabled, node and edge counts, everything the validator says is wrong or suspicious, whether there are unsaved changes, which node is selected, and the last run status. Also returns the NODE TYPE vocabulary — each type's id, what it is for, and the exact handle ids an edge may leave it from. Call this FIRST, before reasoning about or changing the workflow. | |
list_nodes | — | List this workflow's nodes — { id, type, label, stepKey, runs, props, addressable }. stepKey is how the node is addressed in a {{steps.…}} token, and it is NOT always the label: a label with a space in it cannot be used in a token at all. Up to ${MAX_LIST}. | |
list_edges | — | List the wires — { id, source, sourceLabel, sourceHandle, target, targetLabel }. sourceHandle is which OUTPUT the wire leaves from: "out" for most types, "true"/"false" on a branch, "body"/"done" on a loop. Also returns the legal handles for every node that has an outgoing wire. | |
list_blocks | q kind limit | SEARCH everything this instance can run — automation actions, adapter functions, stored function calls, this API's own endpoints, and scanned project functions. Returns { kind, ref, label, hint }; kind and ref are exactly what set_node_executor takes. This SEARCHES rather than dumps: there are thousands, so pass a q. Up to ${MAX_BLOCKS} per call, with the true total so you know whether to narrow. | |
describe_block | kind ref | Read one block's declared arguments before binding it: { inputs, outputs, signature, description, source }. source says where the declaration came from. Call this before set_node_props — inventing an argument name is the single most common way a workflow node runs and does nothing. | |
read_last_trace | — | What the last run or step actually did, node by node — { label, ok, ms, output \ | error }. Outputs are truncated. This is the ONLY certain source of what a node produces, because almost nothing in this system declares a return type. |
list_data_sources | nodeId | The exact {{…}} tokens that are LEGAL in one node's arguments, with where each came from: "ran" (an upstream node produced exactly this last run — certain), "declared" (it says it returns this), "structural" (always true here, e.g. the run input, or {{item}} inside a loop). ALWAYS call this before set_node_props on a node that reads an earlier step. A token naming a node that has not run, or a label with a space in it, silently resolves to an empty string and the run still reports success. | |
step_workflow | reset | Advance the SAVED workflow by exactly one node and return what that node produced. This is how you find out what a block really returns — almost nothing in this system declares a return type, so an observed output is the only certain one. Call it repeatedly to walk the graph; pass reset to start over. It refuses while there are unsaved changes, because it runs what is saved, not what is on screen. It does NOT run the whole workflow — ask the operator to press Run for that. | |
add_node | type label after sourceHandle | Add a node to the draft. after wires it up in the same breath — pass the id of the node it should follow, and sourceHandle when that node is a branch ("true"/"false") or a loop ("body"/"done"). A node added without after is not connected to anything and will never run. DOES NOT SAVE. | |
remove_node | id | Remove a node and every wire touching it. The start node cannot be removed — a run has to begin somewhere. DOES NOT SAVE. | |
set_node | id label ms path max workflowId filter | Change a node's own settings in the draft: its label, a wait's milliseconds, a loop's array path and cap, a workflow node's target, a branch's condition. Pass only what you are changing. DOES NOT SAVE. | |
set_node_action | id actionId | Bind an action node to an automation action by id. Clears any executor on that node — a node can only run one thing, and the host prefers the action, so leaving both would silently ignore the executor. DOES NOT SAVE. | |
set_node_executor | id kind ref config | Bind an action node to ANYTHING callable — an adapter function, an endpoint on this API, a stored function call, a scanned project function, an MCP tool, a grid cell, a URL. Use list_blocks to find the kind and ref. Clears any actionId on that node. DOES NOT SAVE. | |
set_node_props | id props merge | Set the arguments a node is called with. A value may be a literal, or a {{…}} token reading an earlier step — {{steps.<stepKey>.output.x}} for another node's result, {{input.x}} for what started the run, {{item}} and {{index}} inside a loop. Call list_data_sources first for the tokens that are actually legal here, and describe_block for the argument names this block expects. merge keeps the props already there. DOES NOT SAVE. | |
connect_nodes | source target sourceHandle | Wire one node into another. sourceHandle picks WHICH output on a node that has more than one — a branch has "true" and "false", a loop has "body" (once per item) and "done" (after the last). Getting it wrong is how half a graph silently never runs. DOES NOT SAVE. | |
disconnect | id source target | Remove a wire, by its edge id or by the pair of nodes it joins. DOES NOT SAVE. | |
set_workflow_meta | name description tags enabled | Change the workflow's name, description, tags, or whether it is enabled. A disabled workflow cannot be started by a trigger. DOES NOT SAVE. | |
auto_layout | — | Lay the graph out left to right, one column per step away from the start. Call it after building — nodes you added land near the middle of the view, so a graph built by tool calls is a pile until this runs. DOES NOT SAVE. | |
save_workflow | — | Persist the draft. This is the ONLY tool that writes — every other change is local until you call it. Call it once, at the end, when the graph is right. Never report work as done before this has returned. | |
select_node | id | Select a node on the canvas and open the inspector on it, so the user is looking at the one you are talking about. Pass no id to clear the selection. | |
open_workflow_panel | panel | Open one of the editor panels so the user can see what you are describing. | |
fit_view | nodeId | Frame the whole graph in the canvas, or one node if you pass its id. Use it after auto_layout, or when the user has lost track of where something is. |
What this domain owns
Which page deals in which thing:
| Page | Species |
|---|---|
dynamic/ssr | dynamic.component |
dynamic/routers | dynamic.router |
dynamic/webhooks | dynamic.webhook |
dynamic/variables | dynamic.variable |
dynamic/jobs | dynamic.job |
dynamic/function-index | dynamic.function |
dynamic/triggers | automation.rule |
dynamic/media | media.station |
dynamic/jobs:history | dynamic.job |
dynamic/jobs:schedules | dynamic.job |
dynamic/webhooks:deliveries | dynamic.delivery |
dynamic/webhooks:outbound | dynamic.webhook |
dynamic/routers:startup | dynamic.router |
dynamic/variables:usage | dynamic.variable |
dynamic/function-index:scans | dynamic.scan |
dynamic/function-index:sources | dynamic.function |
dynamic/triggers:feed | automation.rule |
dynamic/triggers:builder | automation.rule |
dynamic/ssr:catalog | dynamic.component |
dynamic/media:registry | media.station |
dynamic/media:my-stations | media.station |
API
Mounted prefixes that serve this domain: /api/dynamic · /api/dynamic/ssr
The full route table is leumas-capabilities/reference/api-routes.md.
Traps
THE DOMAIN SPLITS IN HALF ON ONE CAPABILITY. The code you wrote — components, routers, scripts, schemas — needs an install that can touch a disk, and on a hosted Studio those routes are ABSENT rather than empty. Everything else works everywhere. Check which half before reporting a missing feature. Only the workflow editor registers live tools. Every other tab is driven through its own API or through the universal /db CRUD, and the two registries this domain owns are admin-only through that lane. CRUD is /db/<collection> (rules, actions, action_types, workflows, iframes); the VERBS are /api/<thing> — /api/automation to fire and simulate, /api/workflows to run and step, /api/jobs to queue, /api/planner to schedule. Running anything general-purpose here is admin-gated, because a workflow node or an action can reach the network and the filesystem with caller-supplied arguments. There are TWO anonymous inbound doors with nearly the same shape, and they belong to different products: one runs a thing you bound to an address, the other fires a trigger rule. Both treat the address itself as a credential; only one of them is rate-limited. A rule is one trigger and a flat list of actions, armed and waiting. A workflow is a graph that runs on demand. A macro is neither — it is recorded mouse and keyboard replayed on the machine. Reaching for the wrong one is the commonest modelling mistake in this domain.