Project Control

The core lifecycle surface for the project a Composer host is running. These endpoints start and stop processing, exit or quit the application, and manage the project file itself — set project-level properties, save, read back the project XML, list the projects available on the host, load one (by name, by upload, or the latest saved version), and inspect the project tree, its limits, or push in supporting assets. This is the section a remote operator or deployment system drives to get a host from "idle" to "playing the right project" and back.

POST /api/start

Starts Composer processing.

Parameters: None

Response:

  • 200 OKProject successfully started!
  • 403 Forbidden — Playback state is not Stopped

POST /api/stop

Stops Composer processing.

Parameters: None

Response:

  • 200 OKProject stop requested!
  • 403 Forbidden — Playback state is not Running

POST /api/exit

Exits the Composer application. Playback must be stopped first.

When to use: This is the standard, preferred shutdown endpoint. Use it from any orchestration or supervision script that drives Composer.

Parameters: None

Response:

  • 200 OKComposer exit called. Application will exit within three seconds.
  • 403 Forbidden — Playback state is not Stopped

POST /api/quit

Exits the Composer application. Only works when playback state is Stopped.

When to use: Functionally near-identical to /api/exit — both require the project to be stopped and both trigger application shutdown. The difference is plumbing: /api/exit sets the per-project ApiExitRequested flag, while /api/quit sets a process-wide ApplicationQuitRequested flag and is dispatched ahead of the normal route table (so it works even if DisableWebApiWhenNotRunning blocks other endpoints). Prefer /api/exit; reach for /api/quit only if you need the early-dispatch behaviour.

Parameters: None

Response:

  • 200 OKApplication quit requested
  • (no error response) — When playback state is not Stopped the call is silently ignored at the early-dispatch layer.

GET /api/project/setproperty

Sets a property on the Project object itself. Use this for project-level settings that aren't reachable via /api/setproperty (which only targets NamedModel instances such as inputs, scenes, operators, and targets). Examples: StartAllInputsOnStart, StartAllTargetsOnStart, UseOptimizedRenderer, VideoFrameRate.

Parameters:

  • property (required) — Public, settable property name on the Project class. Case-sensitive.
  • value (required) — String form of the value. Converted to the property's type via TypeDescriptor.GetConverter.

Response:

  • 200 OKProject.{property} set to {value}
  • 400 Bad Request — Missing property or value, or no such settable property on Project.
  • 500 Internal Server Error — Conversion or setter exception (returned message includes the exception text).

GET /api/project/save

Saves the current project to disk. Without parameters, overwrites the running .prj file (the one Composer was launched with). With path, performs a "Save As" to a different file without changing the running project's file association.

Parameters:

  • path (optional) — Absolute file path to save to. Parent directory must exist. Does not change RunningProjectFileName, so subsequent saves without path still target the original file.

Response:

  • 200 OKProject successfully saved! (no path)
  • 200 OKProject successfully saved to {path} (with path)
  • 200 OKError! No project file has been defined! (no path and no running project file is set)
  • 400 Bad RequestError! 'path' must include a directory.
  • 400 Bad RequestError! Directory does not exist: {directory}
  • 500 Internal Server ErrorError saving project: {message}

GET /api/project/getxml

Returns the raw XML content of the current project file.

Parameters: None

Response:

  • 200 OK — Project XML content (Content-Type: text/xml)
  • 400 Bad Request — Error reading project file

POST /api/project/loadlatestversion

Triggers Composer to reload the project from disk if a newer version is available, or if the current project file has been modified.

What "the latest version" means

Composer's project management feature uses a versioned filename convention:

<projectName>-version-<6 digits>.prj

For example: MyProject-version-000017.prj. New revisions are saved as new files with a higher 6-digit version suffix (...000017.prj, ...000018.prj, etc.) in the same folder as the current project.

When this endpoint is called, Composer:

  1. Scans the project's folder for files matching the version pattern of the currently loaded project, and picks the one with the highest version number.
  2. If that version is newer than what's currently loaded, queues it for loading.
  3. Also checks whether the current project file's modification timestamp has changed on disk (a "touch" reload of the same file). If so, queues a reload of the current file.
  4. If no newer version exists and the current file is unmodified, no reload is performed and a log entry indicates so.

The reload itself is performed asynchronously by the runtime — the response returns as soon as the request has been forwarded.

Parameters: None

Preconditions:

  • PM_EnableProjectManagement must be true
  • PM_AutoReloadNewVersionUsingApi must be true
  • The runtime must be in Running state with at least one frame rendered (otherwise the request is silently ignored).

Response:

  • 200 OKApi Request /api/project/loadlatestversion forwarded to Composer
  • 400 Bad Request — Project management not enabled or auto-reload not enabled

GET /api/project/list

Lists .prj files in the directory of the currently-loaded project. Returns an empty list if the directory contains no other projects; returns 400 if no project is loaded.

Response: 200 OK — JSON array:

[
  { "name": "Show.prj", "fullPath": "C:\\Projects\\Show.prj", "size": 18432, "lastModified": "2026-05-15T09:31:18Z", "extension": ".prj", "isCurrent": true },
  { "name": "Backup.prj", "fullPath": "C:\\Projects\\Backup.prj", "size": 17984, "lastModified": "2026-05-12T14:05:00Z", "extension": ".prj", "isCurrent": false }
]

Entries are ordered newest-first by lastModified. isCurrent flags the entry that matches Project.RunningProjectFileName.

GET /api/project/load

Loads a named .prj file from the current project's directory. The project must be stopped first (POST /api/stop); the server returns 409 Conflict if it's running.

Parameters:

  • file (required) — File name without path. Must end with .prj. Must live in the current project's directory (path traversal is blocked).

Response: 200 OK on success. 400 Bad Request for missing / malformed file. 404 Not Found if the file doesn't exist. 409 Conflict if playback is running. 500 Internal Server Error if Project.LoadProjectFromFile returns false.

Emits a project.load SSE event on success.

POST /api/project/upload

Uploads a new .prj file into the current project's directory. The request body is the file's raw bytes (no multipart wrapping). Existing files are overwritten unless overwrite=false.

Parameters:

  • filename (required) — Target file name without path. Must end with .prj.
  • load (optional) — When true, the server loads the uploaded project immediately after the write. Requires playback to be stopped; otherwise the upload succeeds but load returns 409.
  • overwrite (optional) — Defaults to true. When false, the upload is rejected with 409 Conflict if a file with the same name already exists (the existing file is left untouched).

Body: Raw .prj bytes (typically application/octet-stream).

Response: 200 OK — JSON { name, fullPath, sizeBytes, loaded }. 409 Conflict if overwrite=false and the target already exists. 413 Payload Too Large if the body (declared via Content-Length or measured mid-stream) exceeds the configured upload cap (default 1 GiB; see Settings.HttpApiMaxUploadBytes). 507 Insufficient Storage if the declared body wouldn't fit on the project volume.

Writes go to a .upload temp file first and rename on success, so an aborted upload leaves the previous version intact.

GET /api/project/limits

Reports the upload-related limits and current project-volume capacity. A client can query this once on entry to reject a too-big upload locally instead of streaming a 1 GiB body just to receive 413.

Response: 200 OK — JSON:

{
  "maxUploadBytes": 1073741824,
  "projectExtensions": [".prj", ".js"],
  "allowedAssetExtensions": [".mp4", ".mov", ".mxf", ".avi", ".mkv", ".webm", ".m4v",
                              ".wav", ".mp3", ".aac", ".m4a", ".ogg", ".flac",
                              ".png", ".jpg", ".jpeg", ".js"],
  "projectDirectoryFreeBytes": 482155593728
}

projectDirectoryFreeBytes is -1 if no project is loaded (so the project volume can't be resolved) or if the OS-level query failed. maxUploadBytes reflects the configured cap; clients should treat it as authoritative rather than caching a constant.

POST /api/project/asset/upload

Uploads an asset (audio / video / image / .js script) into the current project's directory. Existing files are overwritten unless overwrite=false.

Allowed extensions: .mp4, .mov, .mxf, .avi, .mkv, .webm, .m4v, .wav, .mp3, .aac, .m4a, .ogg, .flac, .png, .jpg, .jpeg, .js. Anything else returns 415 Unsupported Media Type.

Size limit: Configured by Settings.HttpApiMaxUploadBytes (default 1 GiB). 413 Payload Too Large if exceeded. 507 Insufficient Storage if the declared body wouldn't fit on disk.

Parameters:

  • filename (required) — Target file name without path; extension must be in the allowlist.
  • overwrite (optional) — Defaults to true. When false, the upload is rejected with 409 Conflict if a file with the same name already exists (the existing file is left untouched).

Body: Raw asset bytes.

Response: 200 OK — JSON { name, fullPath, sizeBytes, loaded: false }. (Asset uploads never auto-load anything.) 409 Conflict if overwrite=false and the target already exists.

GET /api/project/tree

Returns the loaded project's hierarchical structure: scenes, layers, operators, and per-scene targets, plus the project-level inputs collection. Designed for browser-based admin UIs that need to render a navigable tree of the project without making many flat /api/objects/list calls and reconstructing containment.

The response is a read-only snapshot taken at request time. To edit a specific component's properties, follow up with /api/objects/getproperties?target=<id> (full property values) or /api/types/schema?type=<typeName> (schema-only, instance-independent).

Parameters: None

Response: 200 OK — JSON object:

{
  "projectFileName": "C:\\Projects\\cloud-keyer.prj",
  "topMostSceneId": "11111111-1111-1111-1111-111111111111",
  "activeSceneId": "11111111-1111-1111-1111-111111111111",
  "scenes": [
    {
      "id": "11111111-1111-1111-1111-111111111111",
      "name": "Scene Input 1",
      "type": "Scene",
      "isTopMost": true,
      "isActive": true,
      "isInternalLivePreviewScene": false,
      "layers": [
        {
          "id": "22222222-2222-2222-2222-222222222222",
          "name": "Sample video",
          "type": "Layer",
          "isVisible": true,
          "input": {
            "id": "33333333-3333-3333-3333-333333333333",
            "name": "Sample video",
            "type": "MediaFileInput"
          },
          "operators": [
            { "id": "44444444-4444-4444-4444-444444444444", "name": "HSV Keyer", "type": "HsvKeyerOperator" },
            { "id": "55555555-5555-5555-5555-555555555555", "name": "LayerTransform", "type": "LayerTransform" }
          ]
        }
      ],
      "targets": [
        { "id": "66666666-6666-6666-6666-666666666666", "name": "MoQ Target #1", "type": "MoqTarget" }
      ]
    }
  ],
  "inputs": [
    { "id": "33333333-3333-3333-3333-333333333333", "name": "Sample video", "type": "MediaFileInput" }
  ]
}

Response fields:

Field Type Description
projectFileName string Full path to the currently loaded .prj file, or empty string if no project is loaded.
topMostSceneId guid? Id of the scene currently rendered to the preview, or null when no scenes exist.
activeSceneId guid? Id of the scene whose properties are currently selected/active, or null.
scenes[] array Scenes in project order. Each scene has id, name, type, isTopMost, isActive, isInternalLivePreviewScene, layers[], and targets[].
scenes[].isInternalLivePreviewScene bool true for the synthetic scene Composer uses internally to drive the live-preview viewport. Admin UIs should typically hide this scene from users — it's not authored content.
scenes[].layers[] array Layers in scene order. Each layer has id, name, type, isVisible, input (the source feeding this layer, or null), and operators[].
scenes[].layers[].input object? The named AbstractInput feeding this layer. Often references an entry from the top-level inputs[] array. null if the layer has no input assigned.
scenes[].layers[].operators[] array Operator chain for the layer, in execution order.
scenes[].targets[] array Per-scene output targets (e.g. MoQ, SRT, RTMP).
inputs[] array Project-level (shared) inputs. Layers reference these by id via their input field.

Every node has id (Guid), name (string), and type (CLR class name).

Errors:

  • 500 Internal Server Error — Exception while enumerating the project (e.g. mid-load state). Caller may retry.