Setting up
Desktop apps
On this page
The NOLGIA plugin for a desktop app connects the app on your computer to NOLGIA. Once it is signed in and Connected is on, you, NOLGIA Agent or any agent holding your token can send it commands: read what is open, run code in the app's own scripting language, render a preview, import a file from your NOLGIA library, export, save, open, and for some apps run the DIT or transcribe clips. The plugin runs each command on your computer and posts the result back.
- Install a plugin from nolgia.ai/plugins: Blender, After Effects, Premiere Pro and Illustrator (one extension for all three), Photoshop, DaVinci Resolve Studio.
- Only your own credentials see your apps and send them commands. NOLGIA Agent reaches an app only while that app's Allow NOLGIA Agent switch is on.
- Commands are free. They cost no credits; generations you make along the way are priced as usual.
- With NOLGIA's MCP server, the same commands are the
nolgia_app_*tools (MCP). This page is the REST API underneath them.
Use the plugin, never outside scripting #
Send every command through the NOLGIA plugin. Never drive the app from outside it: no AppleScript or osascript, no COM, no Windows UI automation, no simulated keystrokes or clicks, no second copy of Blender run from a terminal on the person's file, and no DaVinciResolveScript from a terminal.
The plugin is what keeps the person in control and their work safe. It lists every command in its Activity list, waits for their approval when Ask before running code is on, runs code inside one undo step where the app has one, holds back the app's own dialogs where the app allows it so nothing waits for a click, catches every script error and hands it back as text with the failing line. A command sent from outside skips all of that, and a bad call that way can crash the app and lose unsaved work: in testing, one malformed AppleScript command sent straight to After Effects crashed it.
The endpoints #
| Method | Path | What it does |
|---|---|---|
| GET | /desktop-apps |
List the desktop apps NOLGIA has a plugin for, with each plugin's status and download. Public, identical for every caller. |
| GET | /desktop-apps/{app}/rules |
Read the rules every agent follows when it works in one desktop app. Public, identical for every caller. |
| GET | /bridge/sessions |
List your desktop apps connected to NOLGIA right now. |
| POST | /bridge/sessions |
Register a desktop app plugin, or check in again (heartbeat). |
| DELETE | /bridge/sessions/{id} |
Disconnect a desktop app session. |
| GET | /bridge/sessions/{id}/next |
Long poll for the next command for this app (plugin side). |
| GET | /bridge/commands |
List the commands most recently sent to your desktop apps, newest first. |
| POST | /bridge/commands |
Send a command to one of your connected desktop apps. |
| GET | /bridge/commands/{id} |
Read a command, optionally waiting for it to finish. |
| POST | /bridge/commands/{id}/result |
Report the outcome of a command (plugin side). |
| POST | /bridge/commands/{id}/cancel |
Cancel a queued or running command. |
POST /bridge/sessions, GET /bridge/sessions/{id}/next and POST /bridge/commands/{id}/result are what the plugin itself calls; DELETE /bridge/sessions/{id} is the plugin switching off. You use the others.
1. Find the app #
GET /bridge/sessions lists the apps connected right now, most recently seen first. A session is live while its plugin has checked in within the last 60 seconds.
curl -s https://api.nolgia.ai/v1/bridge/sessions -H "Authorization: Bearer $NOLGIA_TOKEN"
# or, with the nolgia CLI:
nolgia api GET /bridge/sessions
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | |
app |
BridgeApp |
Yes | |
app_version |
string | Yes | |
plugin_version |
string | Yes | |
machine_name |
string | Yes | The computer's name, as the plugin reports it. |
os |
BridgeOS |
No | |
document |
BridgeDocument |
Yes | |
capabilities |
array of string | Yes | Command kinds the plugin runs, as it listed them. info and run are implied, listed or not. |
allow_agent |
boolean | Yes | False when the person switched off "Allow NOLGIA Agent" in the plugin; the NOLGIA Agent's commands are then refused. |
last_seen_at |
string | Yes | The plugin's latest check in. The session is live while this is within 60 seconds. |
An empty list means no app is connected: install the plugin, sign in and turn on Connected. capabilities lists the command kinds that plugin runs; info and run are always there.
2. Send a command #
POST /bridge/commands queues a command for one app. Without session_id it goes to the app's most recently seen live session; pass session_id when two windows of the same app are connected.
| Field | Type | Required | Description |
|---|---|---|---|
app |
BridgeApp |
Yes | |
session_id |
string | No | A live session of app to send it to. Default is the most recently seen live one. |
kind |
string | Yes | info, run, or one of the session's capabilities. |
args |
object | No | The command's arguments, at most 256 KB as JSON, each kind its own (https://docs.nolgia.ai/guides/desktop-apps.html). Default {}. |
timeout_seconds |
integer | No | Seconds until the command expires if the app has not finished it. |
curl -s https://api.nolgia.ai/v1/bridge/commands \
-H "Authorization: Bearer $NOLGIA_TOKEN" -H "Content-Type: application/json" \
-d '{"app": "blender", "kind": "run", "args": {"language": "python", "code": "import bpy\nresult = [o.name for o in bpy.data.objects]"}}'
# or: nolgia api POST /bridge/commands --body @command.json
The answer is the command, 201 and queued. Refusals carry a code:
| Status | code |
Meaning |
|---|---|---|
| 409 | app_not_connected |
No live session of app, or the named session is not live. |
| 422 | language_not_supported |
A run in a language the app does not run. |
| 422 | capability_not_supported |
The plugin does not run this kind: update it. |
| 403 | agent_not_allowed |
NOLGIA Agent sent it and Allow NOLGIA Agent is off. |
| 429 | More than 120 commands in a minute. |
args is at most 256 KB as JSON. timeout_seconds (5 to 900, default 120) sets expires_at: give a command that waits for the person's approval, a long render or a DIT offload more time.
3. Read the result #
GET /bridge/commands/{id}?wait=25 holds the request until the command finishes or 25 seconds pass, then answers with the command as it is. Ask again until status is no longer queued or running.
while :; do
cmd=$(nolgia api GET /bridge/commands/$ID --query wait=25)
status=$(printf '%s' "$cmd" | python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])')
case "$status" in queued|running) ;; *) printf '%s\n' "$cmd"; break ;; esac
done
status |
Meaning |
|---|---|
queued |
Waiting for the plugin to pick it up, normally within moments. |
running |
The app is running it, or the plugin is waiting for the person's approval. |
succeeded |
Done: result holds what the plugin returned. |
failed |
It did not work, or the person clicked Deny: error says which, and for run the line that failed. |
expired |
expires_at passed first, or the app disconnected. |
cancelled |
You cancelled it. |
POST /bridge/commands/{id}/cancel cancels a queued or running command. The app may already have made changes; cancelling does not undo them. GET /bridge/commands lists recent commands (never their args, because a run carries code).
Command kinds #
kind |
args |
Apps |
|---|---|---|
info |
none | All. What is open: the document or project, its comps, sequences, layers, scenes or timelines, the selection. Start every piece of work with it. |
run |
code (required), language (optional, the app's own), timeout_seconds |
All. Code in the app; see below. |
preview |
width (16 to 1920); Blender camera, frame, engine; After Effects comp and frame; Premiere Pro sequence and frame; Illustrator artboard; Photoshop document, region; DaVinci Resolve timeline, frame |
All. A picture of the document, saved to your NOLGIA library. |
import_asset |
asset_id (a NOLGIA library asset), as (Blender plane, texture or clip; After Effects footage or layer; Premiere Pro clip; Illustrator embed or link; Photoshop layer, pixels or document); color_preset (a NOLGIA color preset slug, installed as a LUT: DaVinci Resolve, Premiere Pro, After Effects); DaVinci Resolve also asset_ids, project_id, bin, append, apply_to, node |
All. |
export |
format: Blender blend, png, mp4, glb; After Effects mp4, png, aep; Premiere Pro mp4, png, prproj; Illustrator png, svg, pdf, ai; Photoshop png, jpg, psd; DaVinci Resolve png, mp4. Also frames (24 or 1-120) and filename |
All. Pictures and video go to your NOLGIA library; project-file formats make a safety copy next to the file. |
save |
path (optional; the open file by default). DaVinci Resolve takes none: it saves the open project. |
All. |
open |
path; DaVinci Resolve project and folder |
All. |
dit |
action (start, status, cancel), subcommand (offload, verify, manifest, inventory, rename, undo, report), sources (one path per camera card), destinations, shoot, dry_run, dir, apply and the rest of nolgia dit |
After Effects, Premiere Pro, Illustrator, DaVinci Resolve. Runs the NOLGIA CLI on the person's computer. |
transcribe |
action, clips, sequence, engine, allow_credits, language, words |
Premiere Pro. |
A plugin checks args before it runs anything and answers a wrong one with an error that names the right choices. GET /desktop-apps/{app}/rules serves the rules every NOLGIA workflow in that app follows (safety copies, keeping work editable, previews, the scripting traps), and they apply to your own commands just as well.
Code in each app #
run carries code in the app's own language. Set result to hand data back; the plugin returns {value, stdout, stderr}, and on failure the error with the line that failed.
| App | language |
What the code sees |
|---|---|---|
| Blender | python |
bpy and the open file. Each run is one undo step. |
| DaVinci Resolve Studio | python |
resolve, project_manager, project, media_pool, timeline (None when no timeline is open) and fusion. Resolve objects in result come back as their names. |
| After Effects, Premiere Pro, Illustrator | extendscript |
The app's ExtendScript DOM. It is ES3: no let, const, arrow functions or JSON. result, or the last expression, is returned. In After Effects each run is one undo step. |
| Photoshop | uxp |
The body of an async function, already inside core.executeAsModal: app, doc (the active document), core, action, batchPlay, play (batchPlay that throws on a failed step), constants, imaging, fs, modal, sleep. Each run is one History step, undone for you when it fails. |
Code cannot be stopped from outside once it runs: after timeout_seconds the plugin stops waiting and says so, and a loop that never ends keeps the app busy until it does. Keep each run to one small step.
The person's approval #
Some commands wait in the plugin for the person to click before they run. While they wait the command is running.
- Ask before running code, a switch in the plugin: every
runshows its code first and waits for Run code or Deny. - DIT writes always wait, whatever that switch says: an offload (Copy and verify), a rename with
apply(Rename files) and an undo (Restore names). A dry run, a verify, an inventory and a report do not. - Opening over unsaved changes in Blender waits too.
A Deny ends the command as failed with an error such as "The person at After Effects clicked Deny, so this did not run." No answer before expires_at ends it as failed with "Nobody approved this in After Effects in time, so it did not run." Tell the person to look at the NOLGIA panel before you send a command that waits, give it a long timeout_seconds, and never send it again on your own after a Deny.
One example per app #
Each body below goes to POST /bridge/commands.
Blender, the objects in the scene:
{"app": "blender", "kind": "run", "args": {"code": "import bpy\nresult = [(o.name, o.type) for o in bpy.context.scene.objects]"}}
After Effects, the comp in the viewer:
{"app": "after_effects", "kind": "run", "args": {"code": "var c = app.project.activeItem;\nresult = (c && c instanceof CompItem) ? {name: c.name, layers: c.numLayers, fps: c.frameRate} : null;"}}
Premiere Pro, the sequences in the project:
{"app": "premiere", "kind": "run", "args": {"code": "var out = [];\nfor (var i = 0; i < app.project.sequences.numSequences; i++) out.push(app.project.sequences[i].name);\nresult = out;"}}
Illustrator, the open document and its artboards:
{"app": "illustrator", "kind": "run", "args": {"code": "result = app.documents.length ? {name: app.activeDocument.name, artboards: app.activeDocument.artboards.length} : null;"}}
Photoshop, the layers of the active document:
{"app": "photoshop", "kind": "run", "args": {"code": "result = doc ? doc.layers.map(l => ({ id: l.id, name: l.name, visible: l.visible })) : null;"}}
DaVinci Resolve Studio, the project and its current timeline:
{"app": "resolve", "kind": "run", "args": {"code": "result = {'project': project.GetName(), 'timeline': timeline.GetName() if timeline else None}"}}
A preview of the Blender scene at frame 1:
{"app": "blender", "kind": "preview", "args": {"frame": 1, "width": 768}}
A DIT plan in Premiere Pro, one path per card, nothing copied:
{"app": "premiere", "kind": "dit", "args": {"action": "start", "subcommand": "offload", "sources": ["/Volumes/A001", "/Volumes/B001"], "destinations": ["/Volumes/RAID/Shoots", "/Volumes/Shuttle"], "shoot": "TEST", "dry_run": true}, "timeout_seconds": 300}
The offload itself is the same body without dry_run: it waits for Copy and verify in the panel and hands back a job_id to follow with "action": "status".

