Docs menu
Reference

REST API

Configure and control a Tangle project over HTTP and JSON. Tokens, scopes, conventions, errors and every route of /api/v1.

tangle-core serves a REST API at http://<host>:7340/api/v1. Every action the designer, dashboards and assistants can take is available here, with the same validation, scope checks and audit log. Request and response bodies use the Tangle contract types (the same JSON as the project file).

Base URL and documentation

WhatWhere
API basehttp://<host>:7340/api/v1
OpenAPI 3.1 documentGET /api/v1/openapi.json (no token needed)
Interactive documentationGET /api/docs (no token needed)

The OpenAPI document is generated from the same route table that serves the API, so it always matches the running core. Its component schemas are the contract types. The /api/docs page loads its viewer from a public CDN, so the browser that opens it needs internet access.

By default tangle-core listens on 127.0.0.1:7340 (this machine only). To accept other machines, start it with --listen 0.0.0.0:7340 --allow-remote --insecure-http (see CLI). There is no TLS yet: use remote access on trusted networks only.

Authentication

Send an API token in the Authorization header:

GET /api/v1/project HTTP/1.1
Host: 127.0.0.1:7340
Authorization: Bearer tgl_0f3c...

Create tokens on the machine that runs tangle-core:

tangle-core token create --name stage-script --scopes read,control
tangle-core token list
tangle-core token revoke stage-script
  • The secret (tgl_ followed by 64 hex characters) is printed once. Only its SHA-256 hash is stored, in tokens.json in the Tangle data folder.
  • Without --scopes, a token gets read,control.
  • A revoked token stops working at once, without restarting tangle-core.
  • For clients that cannot set headers (a browser EventSource), pass the token as ?token=<token> in the query string.
  • A missing, unknown or revoked token gets 401 with a WWW-Authenticate: Bearer header.

For a core installed as a boot service, manage its tokens with sudo tangle-core token ... (macOS, Linux). See CLI.

Note: REST and MCP access for third-party software is part of Tangle Pro and Ultimate (and the demo mode). On other editions these requests get 403 not_licensed. The built-in designer and dashboards always work. See Licensing.

Scopes

ScopeAllows
readRead the project, live state, timeline states, alerts, metrics, license, events.
controlSend intents, drive timelines, run automations, acknowledge and silence alerts, push metrics, resume a paused session.
configureChange the project (create, replace, delete resources), test devices, scan the network, edit and publish dashboards, test notification channels.
adminAdministration: licensing (activate, sign in, refresh, deactivate, offline), pairing and accepting cores.
liveLets an MCP assistant change things while a timeline plays. It has no effect on REST. See MCP.

Scopes do not include each other: a token that should read and configure needs read,configure. A token without the scope a route needs gets 403 missing_scope.

Dashboard sessions

Browsers that open a dashboard get a short-lived session token from POST /api/v1/dashboards/{id}/session (no token needed to call it). Session tokens start with tgd_, last 12 hours and carry the read and control scopes. See Dashboards.

Conventions

JSON and resource ids

  • Send content-type: application/json. A body that is not JSON gets 400 invalid_json.
  • Every resource has an id: lowercase letters, digits, ., _ and -, starting with a letter or digit, at most 128 characters.
  • Times are milliseconds since the Unix epoch (UTC), unless a field says otherwise.
  • Cross-origin requests are allowed from any origin (requests carry bearer tokens, never cookies). The ETag header is exposed to browsers.

Errors

Errors use RFC 9457 problem details, with content type application/problem+json and a stable machine-readable code:

{
  "type": "https://tangle.dev/problems/device_not_found",
  "title": "Not found",
  "status": 404,
  "detail": "device proj-9 does not exist",
  "code": "device_not_found"
}

Validation errors (422) add an errors array with one entry per problem. not_primary adds primary.

StatusCodeMeaning
400invalid_jsonThe body is not JSON.
400invalid_wait?wait= is not like 5s or 500ms.
400topics_requiredGET /events without ?topics=.
400unsupportedThe timeline does not support this transport operation.
401unauthorizedMissing, unknown or revoked token, or expired dashboard session.
401pin_required, wrong_pinDashboard session: PIN missing or wrong.
403missing_scopeThe token lacks the scope this route needs.
403not_licensedThe edition does not include this (external API, monitoring, or the change exceeds a license limit).
403panels_onlyThe dashboard accepts enrolled panels only.
404<kind>_not_foundFor example device_not_found, timeline_not_found, intent_not_found, automation_not_found.
404no_draftPublishing a dashboard that has no draft.
409already_existsPOST with an id that is already used.
409timeline_runningEditing a timeline (or its tracks and cues) while it plays, without allowLive.
409show_modeNetwork scan while a timeline plays.
409show_runningMCP only: a change while a timeline plays, without the live scope.
409not_primaryThis node is a redundancy backup. primary holds the primary's address.
409disabledRunning a disabled automation.
409not_pausedResuming when no designer or demo session is paused.
409busy, conflictThe project changed too fast; try again.
412precondition_failedIf-Match does not match the current ETag.
422invalidThe body, or the project as it would be after the change, is invalid.
429too_many_attemptsMore than 5 wrong dashboard PINs in a minute.
503unavailableA service is not ready (stopped player, storage not configured, credential store error).

ETags and If-Match

GET on a single resource (and on /project) returns a strong ETag. Send it back in If-Match on PUT or DELETE to make sure nobody changed the resource since you read it. If it changed, the request fails with 412 precondition_failed and nothing is applied.

  • If-Match: * matches any existing version.
  • A PUT with an If-Match (other than *) on a resource that does not exist fails with 412.
  • Successful writes return the new ETag.

Dry runs

Add ?dryRun=true (or ?dryRun, ?dryRun=1) to any create, replace or delete. Core validates the change against the whole project and returns what would happen, without applying it:

{
  "dryRun": true,
  "result": { "id": "pj-2", "name": "Projector 2", "moduleId": "misc.pjlink.projector", "room": "hall" },
  "changes": [{ "op": "create", "kind": "device", "id": "pj-2" }]
}

result is the resource as it would be (absent for a delete). changes lists contract ProjectChange entries (op: create, update or delete; kind; id; parent for tracks and cues, such as show or show/lights).

How changes are applied

  • Each write is atomic. Core applies the change to a copy of the project, validates the whole result (schema and cross-references, such as a scene pointing at a device that no longer exists), then swaps it in. A mistake changes nothing and the 422 lists each problem.
  • Changes are saved to the project file (JSON or YAML, following its extension) and recorded in audit.log in the log folder, with the token name and surface.
  • Devices are started or stopped to match.
  • Device settings are checked against the module's configSchema. Secret settings (marked x-secret in the module) are removed from the body and stored in the OS credential store, never in the project file.
  • Notification channel secrets (passwords, tokens, webhook URLs) also go to the credential store. The project keeps ********; send ******** back to keep the stored value.
  • A dashboard PIN sent as access.pin is stored as a salted hash and never returned.
  • Every project change publishes a project.changed event.

Live timelines

Editing a timeline, or one of its tracks or cues, while it plays is refused with 409 timeline_running. Add ?allowLive=true to edit it anyway. Network discovery is always refused while any timeline plays (409 show_mode), because a scan floods the show network.

MCP assistants have an extra protection: without the live scope, every change and control action is refused while a timeline plays. REST clients are not subject to it. See MCP.

Redundancy: 409 not_primary

In a redundant cluster, only the primary accepts writes and control. A backup answers changes, intents, transport, automation runs, and alert acknowledgement or silencing with 409:

{
  "type": "https://tangle.dev/problems/not_primary",
  "title": "Conflict",
  "status": 409,
  "detail": "this node is a backup; send changes to the primary",
  "code": "not_primary",
  "primary": "http://10.0.0.1:7340"
}

primary is null while the backup does not know the primary yet. Reads work on every node. See Redundancy.

Routes

Scope is the scope the token needs. Paths are relative to http://<host>:7340.

Project and modules

MethodPathScopeDescription
GET/api/v1/projectreadThe whole project (contract Project), with an ETag. Dashboard PIN hashes are left out.
GET/api/v1/modulesreadInstalled modules: [{ "manifest": ModuleManifest, "devices": ["pj-1", ...] }].
GET/api/v1/modules/catalogreadInstalled modules and the modules available to install (folders scanned on each call).
POST/api/v1/modules/catalog/{id}/installconfigureInstall or update a module from the catalog; devices using it restart.
POST/api/v1/project/saveconfigureWrite the open project's file now (changes are also saved as they are made). Returns { savedAt, path }.
PUT/api/v1/project/variablesconfigureDeclare the project's variables (a list of ProjectVariable).
PUT/api/v1/project/unitsconfigureThe project's units: metric or imperial, and overrides per quantity (UnitPreference).
PUT/api/v1/project/controlsconfigureThe default dashboard control style and the highlight colours of each style.
PUT/api/v1/project/clusterconfigureHow the cluster shares the work: standby or shared mode, folder placement, auto return, node health limits (ClusterSettings).
GET/api/v1/unitsreadEvery quantity and unit (°C, °F, W, kW, h…) with how to convert between them.
GET/api/v1/templatesreadProject templates: the kinds of project the wizard can start from.
POST/api/v1/templates/{id}/applyconfigureAdd a template's rooms, planned devices, timelines, automations, dashboards and alert rules to the project (body: answers, devices; ?dryRun).

Project library

MethodPathScopeDescription
GET/api/v1/projectsreadThe library (ProjectLibrary): the open project (loaded: false when none) and every project on this core.
POST/api/v1/projectsconfigureCreate an empty project (ProjectCreateRequest: name, optional id, open).
POST/api/v1/projects/importconfigureImport a .tangle file or a project .json/.yaml as a new project: the file as the body, ?name=, ?open=true.
POST/api/v1/projects/{id}/openconfigureOpen a project; the open one closes. Refused with 409 show_running while a timeline plays, unless { "force": true }. Answers 202; clients get project.switching, then project.changed.
POST/api/v1/projects/{id}/duplicateconfigureCopy a project with its dashboards and plans under a new name; open: true is Save as.
POST/api/v1/projects/{id}/renameconfigureGive a project a new name (ProjectRenameRequest: name), open or not. Its id, dashboards and plans stay. Answers { "project": ProjectSummary }; renaming the open one sends project.changed.
GET/api/v1/projects/{id}/exportreadOne .tangle file (a zip: project, dashboards, plans, module lock; no device passwords).
DELETE/api/v1/projects/{id}configureDelete a project, its dashboards, plans and alert history (not the open one).
POST/api/v1/project/closeconfigureClose the open project: devices stop and changes are refused until another opens. Same force rule as open.

Project resources

Each resource type supports the same five routes:

MethodPathScopeDescriptionSuccess
GETcollection pathreadList all resources of this type.200 with an array
POSTcollection pathconfigureCreate one. The body must include id.201 with the resource, Location and ETag
GETitem pathreadRead one.200 with the resource and ETag
PUTitem pathconfigureCreate or replace one with a complete new version. The body id may be left out; if given it must match the path.200 (replaced) or 201 (created), with ETag
DELETEitem pathconfigureDelete one.204

Create, replace and delete accept ?dryRun and ?allowLive; replace and delete accept If-Match.

ResourceCollection pathItem pathBody (contract type)
Rooms/api/v1/rooms/api/v1/rooms/{id}Room
Devices/api/v1/devices/api/v1/devices/{id}DeviceInstance
Scenes/api/v1/scenes/api/v1/scenes/{id}Scene
Timelines/api/v1/timelines/api/v1/timelines/{timeline}Timeline
Tracks/api/v1/timelines/{timeline}/tracks/api/v1/timelines/{timeline}/tracks/{track}Track
Cues/api/v1/timelines/{timeline}/tracks/{track}/cues/api/v1/timelines/{timeline}/tracks/{track}/cues/{id}Cue
Dashboards/api/v1/dashboards/api/v1/dashboards/{id}Dashboard
Automations/api/v1/automations/api/v1/automations/{id}Automation
Alert rules/api/v1/alertRules/api/v1/alertRules/{id}AlertRule
Notification channels/api/v1/notificationChannels/api/v1/notificationChannels/{id}NotificationChannel
Maintenance windows/api/v1/maintenanceWindows/api/v1/maintenanceWindows/{id}MaintenanceWindow
Probes/api/v1/probes/api/v1/probes/{id}Probe
Listeners/api/v1/listeners/api/v1/listeners/{id}Listener
Maps/api/v1/maps/api/v1/maps/{id}Map
End-user views/api/v1/views/api/v1/views/{id}EndUserView
Lighting sequences/api/v1/sequences/api/v1/sequences/{id}Sequence
Lighting presets/api/v1/presets/api/v1/presets/{id}LightingPreset
Lighting groups/api/v1/lightingGroups/api/v1/lightingGroups/{id}LightingGroup
Executors/api/v1/executors/api/v1/executors/{id}Executor

The fields of each type are described in Project file. Notes per resource:

  • Tracks and cues live inside their timeline: a missing parent timeline or track gets 404. Changing them counts as changing the timeline (timeline_running applies).
  • Dashboards: a POST or PUT publishes the dashboard directly. Core increases version, keeps the published version in the dashboard's history, and screens showing it reload. To edit without affecting screens, use the draft routes.
  • Notification channels: Slack and Teams channels need settings.webhookUrl.

Live state

MethodPathScopeDescription
GET/api/v1/snapshotreadEvery device's status and state, and every timeline's state (contract Snapshot: { devices: [DeviceSnapshot], timelines: [TimelineState] }).
GET/api/v1/devices/{id}/statereadOne device: { device, status, state }. status is one of connecting, online, degraded, offline, timeout, error; state maps capability id to state values.
GET/api/v1/state/timelinesreadTransport state of every timeline (array of TimelineState).
GET/api/v1/state/listenersreadEvery listener: open or not, clients, message counts.
GET/api/v1/cluster/placementreadShared mode: which node runs each device, and which devices moved off their preferred node.
POST/api/v1/cluster/rebalancecontrolShared mode: spread the devices across the nodes again and bring moved devices back (dryRun to preview).
GET/api/v1/clusterreadRedundancy status (contract ClusterStatus): this node's role, the primary, quorum, clock offset and every node's health, sync state and cue log. A standalone node answers { "role": "standalone", "nodes": [] }.
GET/api/v1/cluster/discoveredreadCores found on the local network that are not in this cluster (contract DiscoveredCore), each with its state: new, separate or clustered. Only a primary or standalone core lists them.
POST/api/v1/cluster/discovered/{id}/acceptadminAccept a discovered core: body { "mode": "separate" } or { "mode": "redundant", "code": "482913" } (contract ClusterAccept). redundant needs that core's pairing code; a wrong code answers wrong_pairing_code. See Redundancy.
GET/api/v1/cluster/pairingadminThis core's current 6-digit pairing code, to type on the main core.
GET/api/v1/eventsreadLive events as Server-Sent Events. See Event stream.

Intents

MethodPathScopeDescription
POST/api/v1/intentscontrolSend an intent (contract IntentRequest).
GET/api/v1/intents/{intentId}readCurrent status of an intent (contract IntentStatus).

Request body:

FieldTypeRequiredDescription
namestringyespower.on, power.off, level.set, mute.on, mute.off, input.select, transport.play, preset.recall (any <capability>.<action> of the standard capabilities power, level, mute, input, transport, preset); scene.recall with params.scene; action with params.capability, params.action, params.params; release to drop priority holds.
targetobjectyes{ "kind": "device", "id" }, { "kind": "room", "id" }, { "kind": "tag", "tag" } or { "kind": "all" }.
paramsobjectnoPassed to the device action. holdMs keeps this intent's priority on the state it sets for that long.
priorityinteger 0-100noHigher wins on conflict. Default 30 for API calls. 90 and above hold until a release.
attimestampnoWhen to run it.
expiresAttimestampnoDrop it if not run by then.

Without ?wait, core answers 202 Accepted as soon as the intent is accepted, with a Location: /api/v1/intents/{intentId} header. With ?wait=5s (or 500ms, or a number of seconds; at most 60 s) it answers 200 with the final status, or the latest status when the time runs out.

Response (IntentStatus):

FieldDescription
intentIdUUID of the intent.
stateaccepted, rejected, scheduled, running, completed, partial, failed or cancelled.
reasonWhy it was rejected or failed.
devices[{ "deviceId", "outcome": "ok" | "failed" | "overridden" | "skipped", "error" }].
updatedAtTimestamp.

Timelines

MethodPathScopeDescription
POST/api/v1/timelines/{timeline}/transportcontrolPlay, pause, stop, seek or GO. Body { "op": "play" | "pause" | "stop" | "seek" | "go", "positionMs" }; positionMs is required for seek. Returns the new TimelineState.

TimelineState fields: timelineId, status (stopped, playing, paused, waiting), positionMs, sampledAt (when positionMs was sampled, for interpolation), lastCueId, nextCueId, chasing, clockLost, anchor. See Timelines.

Devices: tests, discovery, logs and removal

MethodPathScopeDescription
GET/api/v1/devices/{id}/logreadThe device's log, newest first: ?limit, ?before, ?kinds (action, status, state, event, note), ?level (info, warning, error).
POST/api/v1/devices/{id}/logcontrolWrite a note into the device's log: { "text", "level" }.
DELETE/api/v1/devices/{id}/logconfigureEmpty the device's log.
GET/api/v1/devices/logreadLogs of several devices merged, newest first: ?devices=a,b and the same filters.
POST/api/v1/devices/referencesreadWhat uses these devices and their sub-devices (body: ids): scenes, cues, automations, maps, dashboards, lighting, alert rules.
POST/api/v1/devices/removeconfigureDelete devices, their sub-devices and their references in one change: body { "ids", "references": "remove" | "keep" } (keep turns their timeline cues into comment cues), ?dryRun.
POST/api/v1/devices/{id}/configurators/{configurator}/runconfigureRun a setup assistant step through the device's module (body: step, answers, config). Settings it returns are saved (secrets to the credential store) and never sent back.
POST/api/v1/devices/{id}/testconfigureTest a device's connection in a separate module process; the running device is not touched. Optional body { "config": { ... } } tests other settings before you save them (stored secrets are filled in). Returns { ok, status, detail, state, elapsedMs }; ok is false when the device did not come online. A test takes 10 s at most.
POST/api/v1/discoveryconfigureScan the network (mDNS, SSDP and module searches). Optional body { "seconds": 1-30 }, default 5. Returns [{ address, name, manufacturer, model, via, service, suggestions }], where suggestions lists matching installed modules. Refused while a timeline plays (409 show_mode).

Dashboards

Besides the resource routes, dashboards have a draft and a version history. Drafts and published versions are kept in the Tangle data folder.

MethodPathScopeDescription
GET/api/v1/dashboards/{id}/draftconfigure{ "draft": true, "dashboard" } when a draft exists, otherwise { "draft": false, "dashboard" } with the published version.
PUT/api/v1/dashboards/{id}/draftconfigureSave a draft (contract Dashboard). Screens keep the published version. Returns { "saved": true }.
POST/api/v1/dashboards/{id}/publishconfigurePublish the draft, or the Dashboard in the body if one is sent. Screens reload to it. Returns { version, dashboard }. 404 no_draft without a draft or body.
GET/api/v1/dashboards/{id}/versionsreadPublished versions, newest first: [{ version, publishedAt, by, name }].
GET/api/v1/dashboards/{id}/versions/{version}readOne published version: { version, publishedAt, by, dashboard }.
POST/api/v1/dashboards/{id}/versions/{version}/restoreconfigurePublish an old version again, as a new version.
POST/api/v1/dashboards/{id}/sessionnoneOpen a dashboard in a browser. Optional body { "pin": "1234" }. Returns { token, expiresAt, dashboardId, access }.

Dashboard checks name the page and widget at fault, for example page main, widget w1: unknown widget type knob.

The session route follows the dashboard's access.mode: open dashboards get a session at once; pin dashboards need the right PIN (401 pin_required or wrong_pin, 429 too_many_attempts after 5 wrong PINs in a minute); panels dashboards answer 403 panels_only (enrolled panels use their own API token). A session stops working when it expires or when the dashboard switches to panels.

Automations

MethodPathScopeDescription
POST/api/v1/automations/{id}/runcontrolRun an automation now. Optional body { "force": true, "params": {} }: force skips its conditions, params fills its params. Returns the contract AutomationRun (runId, automationId, state, trigger, reason, step, startedAt, endedAt): started, or skipped with a reason. 409 not_primary on a backup.
POST/api/v1/automations/{id}/stopcontrolStop every run of the automation in progress.
GET/api/v1/automations/{id}/runsreadThe last 50 runs, newest first (kept in memory).
POST, GET/api/v1/macros/{id}/run, /runs, /stopcontrol, readDeprecated aliases (macros are automations since contract 0.2.30). run does not check conditions.

See Automations.

Variables, controls and functions

MethodPathScopeDescription
GET/api/v1/variablesreadProject variables with their current values.
GET/api/v1/variables/{name}readOne project variable.
PUT/api/v1/variables/{name}controlSet a project variable: { "value": ... }. Variable triggers fire.
GET/api/v1/controlsreadDashboard controls with a kept value or override (value, label, visible, enabled, props).
GET/api/v1/controls/{id}readOne control's state.
PUT/api/v1/controls/{id}controlSet a control (ControlSetRequest: value, label, visible, enabled; null clears). Every screen follows and control triggers fire.
POST/api/v1/controls/{id}/eventscontrolReport a control event (press, release, click or hold); control triggers waiting for it fire.
POST/api/v1/controls/{id}/renameconfigureRename a control: { "to" }. Every reference in the project and in dashboard drafts follows.
GET/api/v1/functionsreadEvery library function (core math, text, time, list, color, and library modules) with its signature.
POST/api/v1/functions/{name}/callcontrolCall a function: { "args": [...] } or named { "args": {...} }. Returns its value.

Listeners

Besides the resource routes:

MethodPathScopeDescription
GET/api/v1/listeners/{id}/logreadMessages the listener received, oldest first: ?since=<seq>&limit=<n>.
GET/api/v1/listeners/{id}/runsreadRecent runs of its rules' steps, newest first.
POST/api/v1/listeners/{id}/testconfigureCheck a message against its rules: { "text" } (or bytes, osc, http), dry run unless "run": true.

See Listeners.

Device creator and community

MethodPathScopeDescription
GET/api/v1/driversreadDrivers made with the device creator.
GET/api/v1/drivers/{id}readA driver's definition and code.
GET/api/v1/drivers/{id}/filesreadEvery file of the driver's module, to download it.
POST/api/v1/drivers/checkconfigureCheck a definition and its code without saving.
POST/api/v1/driversconfigureSave a driver: builds and installs its module; devices using it restart.
DELETE/api/v1/drivers/{id}configureDelete a driver: uninstalls its module and removes its source. 409 driver_in_use (with devices) while devices use it.
GET/api/v1/communityreadThe community server and who is signed in on this core.
POST/api/v1/community/sign-inconfigureSign in with the license account's email and password.
POST/api/v1/community/registerconfigureChoose the username (namespace) at the first sign-in.
POST/api/v1/community/sign-outconfigureSign out on this core.
POST/api/v1/community/password-resetconfigureEmail a password reset link.
GET/api/v1/community/searchreadSearch drivers: ?q=, scope=mine (default) or scope=all.
POST/api/v1/community/installconfigureInstall or update a community driver; devices using it restart.
POST/api/v1/community/publishconfigurePublish a device creator driver under your namespace.

See Device creator.

Maps, plans and images

MethodPathScopeDescription
POST/api/v1/plansconfigureImport a map plan: the file as the body (SVG, DXF, DWG, PNG or JPEG), ?name=. DWG needs a converter on the core's computer.
GET/api/v1/plans/{file}readA stored plan file.
GET/api/v1/maps/{id}/positionsreadPlaced devices and live positions from tracking sources.
POST/api/v1/positionscontrolPush positions from a tracking source: 1 to 1000 positions, in map metres. Returns { accepted, rejected }.
POST/api/v1/assetsconfigureUpload a dashboard image (PNG, JPEG, WebP, GIF or SVG, 10 MB at most): the file as the body, ?name=.
GET/api/v1/assets/{file}readA stored dashboard image. Its name is its content hash, so it never changes.
GET/api/v1/views/publicnoneThe end-user views a browser can open (id, name, skin, theme, access mode).
POST/api/v1/views/{id}/sessionnoneOpen an end-user view in a browser; returns a session token.

Lighting

MethodPathScopeDescription
GET/api/v1/lighting/statereadEvery sequence's live cue, level and pause, the grand master, and your programmer.
POST/api/v1/lighting/commandcontrolPlay lighting: go, back, goto, off, fader, flash, pause a sequence, or the grand master.
POST/api/v1/lighting/programmercontrolChange your programmer: select lights, set values, fan, blind, highlight, clear.
POST/api/v1/lighting/storeconfigureStore the programmer into a cue, a preset or a group.
GET/api/v1/lighting/libraryreadSearch the bundled fixture library: ?q=<manufacturer or model>&limit=50.

Sentinel (monitoring)

Monitoring is part of Tangle Pro and Ultimate. See Sentinel.

MethodPathScopeDescription
GET/api/v1/sentinelreadOverview: active, licensed, counts (critical, warning, info, pending), firing alerts, rooms, health of devices, cores and probes, metrics count, notificationsQueued.
GET/api/v1/sentinel/alertsread{ "alerts": [Alert] } (pending and firing). ?history=true adds history (recent transitions, newest first); ?limit= caps it (default 100).
POST/api/v1/sentinel/ackcontrolAcknowledge an alert: { "alertId", "note" }. Stops repeat notifications. Returns the alert.
POST/api/v1/sentinel/silencecontrolSilence one alert or every alert of a rule: { "alertId" } or { "ruleId" }, plus until (timestamp in the future, required). Returns { until, alerts, ruleId }.
POST/api/v1/sentinel/metricscontrolPush custom metric samples: [{ "name", "value", "labels", "at" }] (1 to 1000 per call; { "samples": [...] } also works). Returns { "accepted": n }.
GET/api/v1/sentinel/metricsreadCurrent custom and probe metric values. ?samples=true adds 24 h of per-minute samples.
POST/api/v1/sentinel/channels/{id}/testconfigureSend a test notification. Returns { ok, channelId, error }.

Alert ids look like <ruleId>/<subject kind>:<subject id>, for example offline/device:proj-1. Acknowledging and silencing run on the primary only (409 not_primary on a backup).

License

MethodPathScopeDescription
GET/api/v1/licensereadContract LicenseInfo: mode (licensed, designer, demo), status (state, edition, entitlements, limits, dates), features, session (paused, endsAt) in designer or demo mode, pendingRestart, notes.
POST/api/v1/license/resumecontrolResume device connections and timelines after the hourly pause of the free designer or demo mode. Returns the new LicenseInfo. 409 not_paused when nothing is paused.
POST/api/v1/license/activateadminActivate this computer with a license key (applies at the next start).
POST/api/v1/license/sign-inadminSign in with the license owner's email and password; lists their licenses, or activates the one chosen.
POST/api/v1/license/password-resetadminEmail a password reset link to a license owner.
POST/api/v1/license/request-fileadminA license request for this computer, to email to Tangle licensing.
POST/api/v1/license/importadminInstall a license file made elsewhere (offline activation, or the answer to a request).
POST/api/v1/license/refreshadminFetch a fresh license file now.
POST/api/v1/license/deactivateadminRemove this computer from its installation.
PUT/api/v1/license/offlineadminOffline mode: never contact the license server.

See Licensing.

Event stream (SSE)

GET /api/v1/events?topics=<list> streams live events as Server-Sent Events. topics is required: a comma-separated list of <event type>:<subject id> or <event type>:*.

curl -N "http://127.0.0.1:7340/api/v1/events?topics=device.state:*,timeline.state:show" \
  -H "Authorization: Bearer $TOKEN"
event: device.state:proj-1
data: {"type":"device.state","deviceId":"proj-1","capability":"tangle.power","values":{"on":true},"at":1760000000000}
  • Each SSE event name is the topic; the data is the event JSON (contract TangleEvent).
  • A client that falls behind gets an event named lagged with {"missed": n}.
  • A keep-alive comment is sent every 15 seconds.
  • The topics are the same as on the WebSocket. See WebSocket events for the list and each event's fields.

In a browser, use EventSource with ?token=:

const es = new EventSource(`/api/v1/events?topics=device.status:*&token=${token}`);
es.addEventListener("device.status:proj-1", (e) => console.log(JSON.parse(e.data)));

Examples

Set a shell variable with your token first:

export TOKEN=tgl_...

Read the project:

curl http://127.0.0.1:7340/api/v1/project -H "Authorization: Bearer $TOKEN"

Recall a scene in a room and wait up to 5 s for the result:

curl -X POST "http://127.0.0.1:7340/api/v1/intents?wait=5s" \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"name":"scene.recall","target":{"kind":"room","id":"room-a"},"params":{"scene":"presentation"}}'

Check a new device before adding it, then add it:

curl -X POST "http://127.0.0.1:7340/api/v1/devices?dryRun=true" \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"id":"pj-2","name":"Projector 2","moduleId":"misc.pjlink.projector","room":"room-a","config":{"host":"10.0.0.21"}}'

curl -X POST http://127.0.0.1:7340/api/v1/devices \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"id":"pj-2","name":"Projector 2","moduleId":"misc.pjlink.projector","room":"room-a","config":{"host":"10.0.0.21"}}'

Update a scene safely (read its ETag, then send it back):

curl -i http://127.0.0.1:7340/api/v1/scenes/presentation -H "Authorization: Bearer $TOKEN"
# ETag: "3f2a9c1d0b7e4a55"

curl -X PUT http://127.0.0.1:7340/api/v1/scenes/presentation \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -H 'If-Match: "3f2a9c1d0b7e4a55"' \
  -d '{"id":"presentation","name":"Presentation","actions":[{"deviceId":"pj-2","capability":"tangle.power","action":"on"}]}'

Start a timeline and jump in a cue list:

curl -X POST http://127.0.0.1:7340/api/v1/timelines/show/transport \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" -d '{"op":"play"}'

curl -X POST http://127.0.0.1:7340/api/v1/timelines/show/transport \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" -d '{"op":"seek","positionMs":90000}'

Push a metric from a script:

curl -X POST http://127.0.0.1:7340/api/v1/sentinel/metrics \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '[{"name":"room.temperature","value":31.5,"labels":{"room":"server"}}]'