http://localhost:3030
GET /api/health
Response:
{
"status": "ok",
"links": 0
}GET /api/config
Response:
{
"exportDir": "X:\\coding\\AE-Link\\exports",
"tempDir": "X:\\coding\\AE-Link\\temp",
"serverPort": 3030
}These endpoints connect to a running DaVinci Resolve instance via the Python scripting bridge.
GET /api/resolve/status
Check if DaVinci Resolve is running and the scripting API is reachable.
Response (connected):
{
"connected": true,
"version": "18.6.4",
"product": "DaVinci Resolve"
}Response (not connected):
{
"connected": false,
"error": "DaVinci Resolve not running or Scripting API not enabled"
}Note: Resolve must be running with the Scripting API enabled (default in Studio). The Python
DaVinciResolveScriptmodule must be findable by the configuredpythonPath.
GET /api/resolve/project
Returns information about the currently open Resolve project.
Response:
{
"name": "My_VFX_Project",
"frameRate": "24.0",
"resolution": { "width": 1920, "height": 1080 },
"colorSpace": "Rec.709",
"timelineCount": 3
}GET /api/resolve/timeline
Returns timeline information including all video tracks and clips.
Response:
{
"name": "Timeline 1",
"frameRate": "24.0",
"resolution": { "width": 1920, "height": 1080 },
"videoTrackCount": 3,
"audioTrackCount": 4,
"currentTimecode": "01:00:12:04",
"tracks": {
"video_1": [
{
"name": "Interview_A",
"start": 0,
"end": 2880,
"duration": 2880,
"sourceStart": 144,
"sourceEnd": 3024,
"mediaPoolItem": "X:\\footage\\interview_a.mov"
}
],
"video_2": [
{
"name": "VFX_Shot_01",
"start": 1440,
"end": 1680,
"duration": 240,
"sourceStart": 0,
"sourceEnd": 240,
"mediaPoolItem": "X:\\footage\\vfx_shot_01.mov"
}
]
}
}GET /api/resolve/selection?track=1
Returns clips from the current timeline. Optionally filter by track index.
Query Parameters:
| Param | Type | Description |
|---|---|---|
track |
number | Filter by video track index (optional) |
Response:
{
"timeline": "Timeline 1",
"fps": 24.0,
"width": 1920,
"height": 1080,
"clipCount": 2,
"clips": [
{
"name": "VFX_Shot_01",
"start": 0,
"end": 240,
"duration": 240,
"sourceIn": 0,
"sourceOut": 240,
"sourcePath": "X:\\footage\\vfx_shot_01.mov",
"trackIndex": 1,
"mediaType": "Video",
"fps": 24.0,
"resolution": { "width": 1920, "height": 1080 }
},
{
"name": "VFX_Shot_02",
"start": 240,
"end": 480,
"duration": 240,
"sourceIn": 0,
"sourceOut": 240,
"sourcePath": "X:\\footage\\vfx_shot_02.mov",
"trackIndex": 1,
"mediaType": "Video",
"fps": 24.0,
"resolution": { "width": 1920, "height": 1080 }
}
]
}Field Reference - Selection Clip:
| Field | Type | Description |
|---|---|---|
name |
string | Clip name in the timeline |
start |
number | Timeline start position in frames |
end |
number | Timeline end position in frames |
duration |
number | Duration in frames |
sourceIn |
number | Source media in-point (left offset) in frames |
sourceOut |
number | Source media out-point (right offset) in frames |
sourcePath |
string | Absolute path to the source media file |
trackIndex |
number | Video track index (1-based) |
mediaType |
string | Media type (Video, Audio, etc.) |
fps |
number | Frame rate of the source clip |
resolution |
object | Source resolution { width, height } |
POST /api/resolve/clip-properties
Returns detailed properties of a specific clip from the media pool.
Request Body:
{
"clipPath": "X:\\footage\\vfx_shot_01.mov"
}Response:
{
"name": "vfx_shot_01",
"properties": {
"File Path": "X:\\footage\\vfx_shot_01.mov",
"Resolution Width": "1920",
"Resolution Height": "1080",
"Type": "Video",
"FPS": "24.0",
"Duration": "01:00:12:04",
"Codec": "H.264"
}
}GET /api/links
Response:
[
{
"id": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"clips": [
{
"name": "VFX_Shot_01",
"linkId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"status": "pending",
"filePath": "X:\\footage\\VFX_Shot_01.mov",
"startTimecode": 0,
"durationFrames": 120,
"start": 0,
"duration": 120,
"sourcePath": "X:\\footage\\VFX_Shot_01.mov",
"sourceIn": 0,
"sourceOut": 120
}
],
"settings": {
"width": 1920,
"height": 1080,
"fps": 24,
"duration": 10,
"renderQueue": "Best Settings"
},
"status": "created",
"exportPath": null,
"createdAt": "2026-07-18T10:43:43.914Z",
"jsxPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...jsx",
"payloadPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...json"
}
]Sends clip data from the Resolve timeline and generates an AE composition script.
POST /api/link-clip
Request Body:
{
"clipData": [
{
"name": "VFX_Shot_01",
"start": 0,
"duration": 120,
"sourcePath": "X:\\footage\\VFX_Shot_01.mov",
"sourceIn": 0,
"sourceOut": 120,
"trackIndex": 1
},
{
"name": "VFX_Shot_02",
"start": 120,
"duration": 96,
"sourcePath": "X:\\footage\\VFX_Shot_02.mov",
"sourceIn": 0,
"sourceOut": 96,
"trackIndex": 1
}
],
"settings": {
"width": 1920,
"height": 1080,
"fps": 24,
"duration": 8
}
}Field Reference - clipData[]:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Display name of the clip |
start |
number | Yes | Timeline position in frames |
duration |
number | Yes | Clip duration in frames |
sourcePath |
string | Yes | Absolute path to the source media file |
sourceIn |
number | No | Source in-point in frames (default: 0) |
sourceOut |
number | No | Source out-point in frames (default: duration) |
trackIndex |
number | No | Video track index in Resolve |
Field Reference - settings:
| Field | Type | Default | Description |
|---|---|---|---|
width |
number | 1920 | Composition width in pixels |
height |
number | 1080 | Composition height in pixels |
fps |
number | 24 | Frames per second |
duration |
number | 10 | Composition duration in seconds |
renderQueue |
string | "Best Settings" | AE render queue preset name |
Response (200):
{
"linkId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"status": "created",
"jsxPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...jsx",
"payloadPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...json"
}Response (400):
{
"error": "clipData array is required"
}Triggers the auto-workflow for a link: if AE is running, queues a job for the CEP extension; if not, launches AE with the generated script.
POST /api/links/:id/auto
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200) - AE running, job queued:
{
"status": "queued",
"jobId": "a1b2c3d4-...",
"message": "Job queued. CEP extension will pick it up automatically."
}Response (200) - AE not running, launching:
{
"status": "sending",
"message": "AE launched with script. It will execute on startup."
}Response (404):
{
"error": "Link not found"
}Response (500):
{
"error": "After Effects not found"
}PUT /api/links/:id/status
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Request Body:
{
"status": "rendered",
"exportPath": "X:\\coding\\AE-Link\\exports\\Resolve_Link_32b0ff89.mov"
}Valid Status Values:
| Status | Description |
|---|---|
created |
Link created, JSX generated |
sending |
AE is being launched or job is being queued |
queued |
Job queued, waiting for CEP extension to pick up |
linked |
AE composition confirmed open |
rendering |
aerender is processing |
rendered |
Render complete, file available |
imported |
Render imported back into Resolve as compound clip |
error |
Something went wrong |
Response (200): Updated link object
Response (404):
{
"error": "Link not found"
}DELETE /api/links/:id
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200):
{
"deleted": true
}Response (404):
{
"error": "Link not found"
}Starts aerender for the specified link's composition.
POST /api/links/:id/render
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200):
{
"status": "rendered"
}Response (500):
{
"error": "After Effects not found"
}Generates a standalone render script that can be run manually in After Effects.
GET /api/links/:id/render-script
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200):
{
"scriptPath": "X:\\coding\\AE-Link\\temp\\render_32b0ff89-....jsx",
"message": "Run this script in After Effects: File > Scripts > Run Script File"
}Generates a render script for the currently active composition in After Effects (called by the CEP extension).
POST /api/render-active-comp
Response (200):
{
"scriptPath": "X:\\coding\\AE-Link\\temp\\render_active_1721312345678.jsx"
}GET /api/render-progress/:id
Returns render progress for a given render ID.
Response:
{
"status": "rendering",
"percent": 65,
"bytes": 1048576
}GET /api/presets — List all presets
POST /api/presets — Create/update a preset
DELETE /api/presets/:name — Delete a preset
POST Request Body:
{
"name": "ProRes 4444",
"template": "Best Settings",
"outputModule": "ProRes 4444"
}GET Response:
{
"presets": [
{ "name": "ProRes 4444", "template": "Best Settings", "outputModule": "ProRes 4444" }
]
}Sends multiple timelines to AE at once.
POST /api/batch-export
Request Body:
{
"timelineNames": ["Timeline 1", "Timeline 2"]
}Response:
{
"results": [...],
"total": 2,
"created": 2
}GET /api/resolve/markers
Returns all markers on the current Resolve timeline.
Response:
{
"timeline": "Timeline 1",
"fps": 24.0,
"markers": [
{
"frame": 120,
"name": "VFX Shot",
"note": "Add particles here",
"color": "#ff0000",
"duration": 1,
"trackType": "video",
"trackIndex": 1,
"clipName": "VFX_Shot_01",
"startTimecode": "00:00:05:00"
}
],
"count": 1
}GET /api/update-check
Checks GitHub for newer versions.
Response:
{
"currentVersion": "1.0.0",
"latestVersion": "1.1.0",
"updateAvailable": true,
"downloadUrl": "https://github.com/..."
}GET /api/perf
Returns timing statistics for all API endpoints (last 100 requests per endpoint).
Response:
{
"uptime": 3600,
"memoryMB": 45,
"endpoints": {
"GET /api/links": {
"count": 120,
"avgMs": 12.3,
"p50Ms": 8.1,
"p95Ms": 45.2,
"p99Ms": 89.0,
"maxMs": 120.5,
"slowCount": 2
}
}
}GET /api/perf/slow
Returns the 10 slowest endpoints sorted by average response time.
Response:
{
"slowest": [
{
"endpoint": "POST /api/batch-export",
"avgMs": 2340.5,
"maxMs": 5120.0,
"slowCount": 5,
"count": 12
}
]
}POST /api/clear
Request Body:
{
"target": "exports"
}Response:
{
"success": true,
"removed": 12
}GET /api/history
Returns recent job history (capped at 100 entries).
Response:
[
{
"type": "create",
"linkId": "32b0ff89-...",
"status": "created",
"clipCount": 3,
"timestamp": "2026-07-19T10:00:00.000Z"
},
{
"type": "import",
"linkId": "32b0ff89-...",
"status": "success",
"file": "Resolve_Link_32b0ff89.mov",
"timestamp": "2026-07-19T10:15:00.000Z"
}
]GET /api/setup — Get auto-detected paths and .env state
POST /api/setup — Write .env configuration
GET Response:
{
"hasEnv": true,
"detected": {
"pythonPath": "C:\\Python314\\python.exe",
"pythonVersion": "3.14.0",
"aePath": "C:\\Program Files\\Adobe\\Adobe After Effects 2026\\Support Files",
"platform": "win32"
},
"config": {
"port": 3030,
"host": "127.0.0.1",
"exportDir": "./exports",
"tempDir": "./temp",
"pythonPath": "python",
"aePath": "",
"scriptingPath": ""
}
}POST Request Body:
{
"PORT": 3030,
"EXPORT_DIR": "./exports",
"PYTHON_PATH": "C:\\Python314\\python.exe",
"AE_PATH_WIN": "C:\\Program Files\\Adobe\\Adobe After Effects 2026\\Support Files"
}Imports a rendered file back into DaVinci Resolve, creating a compound clip with the render over the originals.
POST /api/import-back
Request Body:
{
"renderedPath": "X:\\coding\\AE-Link\\exports\\Resolve_Link_32b0ff89.mov"
}Response (200):
{
"status": "success",
"disabledClips": 3,
"renderedFile": "X:\\coding\\AE-Link\\exports\\Resolve_Link_32b0ff89.mov",
"compoundCreated": true,
"trackIndex": 1
}Response (500):
{
"error": "DaVinci Resolve not running"
}The import-back process:
- Imports the rendered file into the Resolve media pool
- Disables all original clips on the target track
- Appends the rendered clip to the timeline
- Creates a compound clip from all timeline items
GET /api/reaper/status
Returns REAPER installation and running status.
Response (installed and running):
{
"installed": true,
"installPath": "C:\\Program Files\\REAPER",
"running": true,
"version": "7.12"
}Response (installed, not running):
{
"installed": true,
"installPath": "C:\\Program Files\\REAPER",
"running": false,
"version": "7.12"
}Response (not installed):
{
"installed": false,
"installPath": null,
"running": false,
"version": null
}POST /api/reaper/link-clip
Sends audio clip data from Resolve to REAPER. Generates a Lua import script, a seconds-based JSON payload, and queues a reaper-create job.
Request Body:
{
"clipData": [
{
"name": "Dialogue_A",
"start": 0,
"duration": 240,
"sourcePath": "X:\\audio\\dialogue_a.wav",
"sourceIn": 0,
"sourceOut": 240,
"trackIndex": 1
},
{
"name": "SFX_Explosion",
"start": 120,
"duration": 96,
"sourcePath": "X:\\audio\\sfx_explosion.wav",
"sourceIn": 0,
"sourceOut": 96,
"trackIndex": 2
}
],
"settings": {
"width": 1920,
"height": 1080,
"fps": 24,
"duration": 10
}
}Response (200):
{
"linkId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"status": "created",
"payloadPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...json",
"importScriptPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...lua"
}The server converts frame-based clip data to seconds-based for REAPER (seconds = frames / fps) and groups clips by track.
Response (400):
{
"error": "clipData array is required"
}POST /api/reaper/:id/reaper-auto
Triggers the REAPER auto-workflow: if REAPER is running, queues a reaper-create job for the Lua callback script; if not, launches REAPER with the generated import script.
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200) - REAPER running, job queued:
{
"status": "queued",
"jobId": "a1b2c3d4-...",
"message": "Job queued. reaper-callback.lua will pick it up automatically."
}Response (200) - REAPER not running, launching:
{
"status": "sending",
"message": "REAPER launched with import script."
}Response (404):
{
"error": "Link not found"
}GET /api/links/:id/reaper-render-script
Generates a standalone Lua script that opens REAPER's render dialog for the specified link.
URL Parameters:
| Parameter | Description |
|---|---|
id |
Link UUID |
Response (200):
{
"scriptPath": "X:\\coding\\AE-Link\\temp\\render_reaper_32b0ff89-....lua",
"message": "Run this script in REAPER: Actions > Show action list > Browse > Run"
}PUT /api/reaper/import-to-resolve
Imports a rendered audio file from REAPER back into DaVinci Resolve. Adds the audio to a new audio track and mutes the old audio clips.
Request Body:
{
"filePath": "X:/coding/AE-Link/exports/MyProject_Timeline1.wav",
"trackName": "REAPER Audio",
"positionFrames": 0
}| Field | Type | Required | Description |
|---|---|---|---|
filePath |
string | Yes | Absolute path to the rendered audio file |
trackName |
string | No | Name for the audio track (default: "REAPER Audio") |
positionFrames |
number | No | Position on timeline in frames (default: 0) |
Response (200):
{
"success": true,
"filePath": "X:/coding/AE-Link/exports/MyProject_Timeline1.wav"
}Response (500):
{
"error": "Bridge command failed"
}The import process:
- Imports the audio file into the Resolve media pool under "REAPER Renders" bin
- Creates a new audio track (if needed)
- Appends the audio clip to the timeline
- Mutes all old audio clips on the timeline
REAPER scripts communicate with the server via the file system rather than HTTP polling:
Jobs are written to exports/reaper-jobs/ as JSON files:
{
"jobId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"type": "execute-reaper",
"payloadPath": "X:\\coding\\AE-Link\\temp\\payload.json"
}Results are written to exports/reaper-results/ as JSON files:
{
"jobId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"status": "completed",
"message": "Import complete"
}Server writes job file ──> exports/reaper-jobs/{uuid}.json
│
Panel polls directory ────────┘
│
Panel reads + deletes file ───┘
│
Panel executes import ────────┘
│
Panel writes result file ────> exports/reaper-results/{uuid}.json
│
Server watches directory ─────┘
│
Server reads + deletes file ──┘
These endpoints are used by the CEP extension and REAPER callback script to poll for and report on jobs.
GET /api/jobs/pending
Returns the next pending job and marks it as dispatched. Called by the CEP extension every 2 seconds.
Response (job available):
{
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "execute-jsx",
"linkId": "32b0ff89-8d3b-4724-a3f4-e273c2547b54",
"jsxPath": "X:\\coding\\AE-Link\\temp\\32b0ff89-...jsx",
"compName": "Resolve_Link_32b0ff89",
"status": "dispatched",
"createdAt": "2026-07-18T10:43:43.914Z",
"dispatchedAt": "2026-07-18T10:43:45.123Z"
}Response (no pending jobs):
{
"jobId": null
}PUT /api/jobs/:jobId/status
Called by the CEP extension to report job progress or completion.
URL Parameters:
| Parameter | Description |
|---|---|
jobId |
Job UUID |
Request Body:
{
"status": "completed",
"result": { "compName": "Resolve_Link_32b0ff89" }
}Or on error:
{
"status": "error",
"error": "Script not found: X:\\temp\\abc.jsx"
}Valid Job Status Values:
| Status | Description |
|---|---|
pending |
Job created, waiting to be dispatched |
dispatched |
Job returned to CEP extension |
executing |
CEP extension is running the script |
completed |
Script executed successfully |
error |
Script execution failed |
Response (200):
{
"success": true
}Response (404):
{
"error": "Job not found"
}When a job completes successfully, the associated link status is updated to rendering and a render watch is started.
ws://localhost:3030
All messages are JSON with the following structure:
{
"type": "event_name",
"payload": { ... }
}Sent on connection. Contains all current links.
{
"type": "init",
"payload": {
"links": [ ... ]
}
}Broadcast when a new link is created.
{
"type": "link:created",
"payload": {
"id": "uuid",
"clips": [...],
"settings": {...},
"status": "created",
"exportPath": null,
"createdAt": "2026-07-18T10:43:43.914Z"
}
}Broadcast when a link's status changes.
{
"type": "link:updated",
"payload": {
"id": "uuid",
"status": "rendering",
"updatedAt": "2026-07-18T10:45:00.000Z"
}
}Broadcast when the file watcher detects a completed render matching a link.
{
"type": "link:rendered",
"payload": {
"id": "uuid",
"status": "rendered",
"exportPath": "X:\\exports\\render.mov",
"updatedAt": "2026-07-18T10:50:00.000Z"
}
}Broadcast when a link is removed.
{
"type": "link:deleted",
"payload": {
"id": "uuid"
}
}Broadcast when the watcher detects a file that doesn't match any active link.
{
"type": "file:new",
"payload": {
"path": "X:\\exports\\unknown_render.mov",
"name": "unknown_render.mov"
}
}Broadcast when the DaVinci Resolve connection status changes (connected/disconnected).
{
"type": "resolve:status",
"payload": {
"connected": true,
"version": "18.6.4",
"product": "DaVinci Resolve"
}
}interface ClipData {
name: string;
start: number; // Timeline position in frames
duration: number; // Duration in frames
sourcePath: string; // Absolute file path
sourceIn?: number; // Source in-point in frames
sourceOut?: number; // Source out-point in frames
trackIndex?: number; // Resolve track index
}interface LinkClip {
name: string;
linkId: string;
status: 'pending' | 'linked' | 'rendering' | 'rendered' | 'error';
filePath: string;
startTimecode: number;
durationFrames: number;
}interface Link {
id: string;
clips: LinkClip[];
settings: {
width: number;
height: number;
fps: number;
duration: number;
};
status: 'created' | 'sending' | 'queued' | 'linked' | 'rendering' | 'rendered' | 'imported' | 'error';
exportPath: string | null;
createdAt: string;
updatedAt?: string;
jsxPath?: string;
payloadPath?: string;
error?: string;
}interface ResolveStatus {
connected: boolean;
version?: string;
product?: string;
error?: string;
}interface ResolveSelection {
timeline: string;
fps: number;
width: number;
height: number;
clips: ResolveClipData[];
clipCount: number;
error?: string;
}interface ResolveClipData {
name: string;
start: number;
end: number;
duration: number;
sourceIn: number;
sourceOut: number;
sourcePath: string;
trackIndex: number;
mediaType: string;
fps: number;
resolution: { width: number; height: number };
}interface Job {
type: 'execute-jsx' | 'reaper-create';
linkId: string;
jsxPath?: string; // For AE jobs
compName?: string; // For AE jobs
payloadPath?: string; // For REAPER jobs (seconds-based JSON)
importScriptPath?: string; // For REAPER jobs (Lua import script)
status: 'pending' | 'dispatched' | 'executing' | 'completed' | 'error';
createdAt: string;
dispatchedAt?: string;
result?: { compName: string } | { projectName: string };
error?: string;
}interface ReaperStatus {
installed: boolean;
installPath: string | null;
running: boolean;
version: string | null;
}interface ReaperLink {
linkId: string;
status: string;
payloadPath: string;
importScriptPath: string;
}See server/config.json:
{
"server": {
"port": 3030,
"host": "localhost"
},
"paths": {
"vfxExportDir": "./exports",
"tempDir": "./temp",
"aeInstallPath": {
"win32": "C:\\Program Files\\Adobe\\Adobe After Effects 2026\\Support Files",
"darwin": "/Applications/Adobe After Effects 2024/"
}
},
"watcher": {
"enabled": true,
"extensions": [".mov", ".mp4", ".exr", ".png", ".tif", ".jpg", ".wav", ".flac", ".aiff", ".ogg", ".mp3"],
"debounceMs": 1000
},
"ae": {
"renderCommand": "aerender",
"projectExtension": ".aep"
},
"resolve": {
"pythonPath": "python",
"pollIntervalMs": 2000,
"bridgeScript": "./resolve-bridge.py"
}
}| Key | Type | Description |
|---|---|---|
server.port |
number | HTTP/WS port |
server.host |
string | Bind address |
paths.vfxExportDir |
string | Directory watched for renders |
paths.tempDir |
string | Directory for generated scripts |
paths.aeInstallPath.win32 |
string | After Effects install path (Windows) |
paths.aeInstallPath.darwin |
string | After Effects install path (macOS) |
watcher.enabled |
boolean | Enable/disable file watcher |
watcher.extensions |
string[] | File extensions to watch |
watcher.debounceMs |
number | Debounce interval for batch detection |
ae.renderCommand |
string | aerender executable name |
ae.projectExtension |
string | AE project file extension |
resolve.pythonPath |
string | Python executable path |
resolve.pollIntervalMs |
number | Resolve connection polling interval |
resolve.bridgeScript |
string | Path to Python bridge script |
reaper.installPath |
object | REAPER install paths { win32, darwin } |
reaper.pollIntervalMs |
number | REAPER callback polling interval |
reaper.renderFormat |
string | Default REAPER render format (e.g. "wav") |