Builder API (Entity Management)

The Builder API is the programmatic counterpart to building a project in Composer Desktop. Instead of dragging inputs, scenes, layers, targets, and operators into place by hand, a client creates, lists, reorders, and deletes them over HTTP against the same live project tree the Desktop editor manipulates. It is paired with the type-discovery endpoints (/api/types/inputs, /api/types/targets, /api/types/operators, /api/types/schema) so a client can enumerate the available component types and their property schemas before constructing entities, then set those properties through the regular /api/setproperty call.

Typical uses are headless provisioning of a Composer Runtime, templated project generation driven by an external system, and automated test setup. It is not intended for everyday operation of a finished show — for that, drive the project with Connectors, /api/setproperty, and /api/invokecommand instead.

Use with caution. This API mutates project structure directly and is aimed at advanced, automated workflows — not interactive use. Keep EnableBuilderApi off on any host that isn't actively building projects over HTTP, validate input against the type-discovery endpoints, and exercise new automation against a throwaway project before pointing it at production state. See the detailed warning below.

All endpoints in this section require EnableBuilderApi to be true in settings — see the settings.xml reference for where to set it. It is off by default.

Advanced users only. The Builder API mutates the live project tree directly — creating, editing, and deleting inputs, scenes, layers, targets, connectors, and their commands. It bypasses the validation, conflict detection, and undo affordances that Composer Desktop applies when you build the same project interactively. Misuse — wrong type names, mismatched IDs, conflicting parent / child relations, properties set to values the component can't accept — can produce faulty project files, persisted settings the UI can't reload, or a project that crashes on next open. Test against a throwaway project before pointing the Builder API at production state, and keep EnableBuilderApi off on hosts that don't actively need it.

GET /api/types/inputs

Lists all available input types with metadata. Uses reflection to enumerate exported types from loaded assemblies.

Parameters: None

Response: 200 OK — JSON array:

[
  {
    "Id": "type-guid",
    "Name": "SrtInput",
    "FullName": "SrtInput.SrtInput",
    "Category": "Network",
    "About": "SRT listener input",
    "CanHaveAudio": true,
    "CanHaveVideo": true,
    "RequiresPlugin": "",
    "CoreAddonGroup": "",
    "PluginLicensed": true
  }
]

License-gating fields (also returned by /api/types/operators):

Field Meaning
RequiresPlugin Plugin ClassName (as registered in VindralLicenseBuilder) this type is gated by, or "" when no plugin license is needed.
CoreAddonGroup Core-license addon group key (e.g. "VTrackRoulette") gating this type under the 2 h Core demo budget, or "" when not addon-gated.
PluginLicensed true when no plugin is required or the running license grants RequiresPlugin; false means the type runs for the trial budget only.

Scope — these fields are not a complete licensing picture. They report only per-component plugin add-on state derived from the component's declared RequiredPluginClassName hook. They do not reflect:

  • Tier-level (Core / Limited Edition) gating. Total input/scene/target/connector caps, the Decklink-input cap, and similar limits are enforced by the engine against the license tier, not surfaced here. A type with PluginLicensed: true can still fail to instantiate at project-build time because a tier cap is hit.
  • Components not loaded under a Core license. On a Core license, dynamically-loaded plug-in component DLLs are not loaded at all, so they won't even appear in this list.
  • Operators that self-check. A few licensed operators (e.g. FaceDetectOperator2, ObjectDetectionOperator) predate the RequiredPluginClassName hook and perform their own internal license checks; they report RequiresPlugin: "" / PluginLicensed: true here despite requiring a license.

Also note a Core license disables the HTTP API entirely (IsHttpApiAllowed() == !LicenseIsCore()), so these endpoints only ever respond on non-Core hosts — PluginLicensed reports whether a plugin add-on is present, never the base tier.


GET /api/types/targets

Lists all available target types with metadata. Mirror of /api/types/inputs — same shape, just for AbstractTarget subclasses.

Parameters: None

Response: 200 OK — JSON array:

[
  {
    "Id": "type-guid",
    "Name": "RtmpTarget",
    "FullName": "RtmpTarget.RtmpTarget",
    "Category": "Streaming",
    "About": "RTMP streaming target",
    "CanHaveAudio": true,
    "CanHaveVideo": true
  }
]

Target types are not license-gated by plugin, so the RequiresPlugin / CoreAddonGroup / PluginLicensed fields are not included here.


GET /api/types/operators

Lists all available operator types with metadata. Operators return Id, Name, FullName, About plus the license-gating fields below (no Category / CanHaveAudio / CanHaveVideo).

Parameters: None

Response: 200 OK — JSON array:

[
  {
    "Id": "type-guid",
    "Name": "BlurOperator",
    "FullName": "BlurOperator.BlurOperator",
    "About": "Gaussian blur operator with adjustable radius and direction",
    "RequiresPlugin": "",
    "CoreAddonGroup": "",
    "PluginLicensed": true
  }
]

RequiresPlugin / CoreAddonGroup / PluginLicensed carry the same meaning as for /api/types/inputs. Plugin-gated operators (e.g. WheelTrackerOperator, RouletteWheelTrackerOperator, CrystalSpeechOperator) report a non-empty value here when the running license lacks the add-on.


GET /api/types/schema

Returns the full property schema for a given input, operator, or target type. Useful for builder UIs that need to render property editors without a running instance.

Parameters:

Name Type Required Description
type string Yes Type name (e.g. BlurOperator, SrtInput, MoQTarget)

Response: 200 OK — JSON object:

{
  "typeName": "BlurOperator",
  "fullName": "BlurOperator.BlurOperator",
  "kind": "operator",
  "category": "Blur and Sharpen",
  "about": "...",
  "canHaveAudio": false,
  "canHaveVideo": true,
  "instanceCreated": true,
  "properties": [
    {
      "name": "Direction",
      "displayName": "Blur direction",
      "propertyType": "enum",
      "clrType": "BlurDirection",
      "canWrite": true,
      "defaultValue": "Both",
      "enumValues": ["Both", "HorizontalOnly", "VerticalOnly"],
      "enumLabels": ["Both", "Horizontal only", "Vertical only"],
      "slider": null,
      "tooltip": null,
      "isAdvanced": false,
      "isDebug": false,
      "isDeveloper": false,
      "isEditorLocked": false,
      "isRequired": false,
      "requiredCondition": null,
      "isAudio": false,
      "isVideo": false,
      "isHidden": false,
      "hideAsApiTarget": false,
      "noSerialization": false,
      "isCommand": false,
      "isSeparator": false,
      "switchLabel": null,
      "expandableSection": null,
      "confirmationRequired": null,
      "isMultiline": false,
      "isFileAsset": false,
      "fileExtensions": null
    }
  ]
}

Property type values: int, float, double, bool, string, enum, stringCollectionEnum, command, commandGroup, separator, description, externalLink, formattedMessage, componentLog, propertyNote, dictionary, connectionStatus, color, fileAsset, object

Composite types:

  • descriptionMultilineText paragraph used by AboutDescription. Live value is { "Text": "..." }.
  • externalLinkExternalUrlLink used by ExternalLink for clickable documentation links. Live value is { "HttpLinkString", "LinkText", "ToolTip" }.
  • commandGroupCommandGroup rendered as a horizontal button strip of 2–4 commands. The descriptor includes a commands array — index-aligned with the group's buttons — where each entry is { "propertyName", "displayName", "tooltip" }. Use propertyName as the command parameter to /api/invokecommand so the button on click invokes the right top-level Command property. Live values for the group are the serialized CommandGroup (its Commands[] array exposes each command's Name and CanExecute).
  • formattedMessage — a single status line. Live value is { "Message", "DateTime", "FormattedMessageType" } where FormattedMessageType is Info / Warning / Error. Render as one severity-tinted line, no label.
  • componentLog — a bounded ring buffer of FormattedMessage entries. Live value is { "Capacity", "LogCache": [ ...FormattedMessage ] }. Render as a vertically scrollable list, each row tinted by severity.
  • stringCollectionEnum — a runtime-populated single-choice dropdown. Live value is { "SelectedValue", "SourceCollection": [ ...string ] }. To change the selection via /api/setproperty, encode the value as selected:opt1/opt2/opt3 — the server-side StringCollectionEnumConverter parses this and replaces the property with a new instance.
  • propertyNote — a hint/note box. Live value is { "IsVisible", "Icon", "Text", "LinkUrl", "LinkText", "MarginTop", "MarginBottom" }. Icon is one of None, Info, Tip, Warning, Error, Success. Render only when IsVisible is true; styling should reflect the icon kind.
  • dictionary — any IDictionary (typically Dictionary<string, object>), used for metadata bags like MoQInput.VideoStreamInfo / AudioStreamInfo. Live value is a JSON object with arbitrary keys; render as a key/value table.
  • connectionStatus — the ConnectionStatus enum (Disconnected / Connecting / Connected / Reconnecting). Distinct from generic enum so clients can match Composer Desktop's status-pill rendering (green dashed border when Connected, etc.) instead of treating it as a dropdown. Live value is the enum name as a string.
  • fileAsset — a Uri-typed file/URL source (e.g. StillImageInput / MediaFileInput SourceUrl, decorated [FileAsset]). Live value is the Uri serialized as its string (empty/absent when no source). Set a new source with /api/setproperty (value=<path-or-url>); /api/setproperty cannot clear it (it rejects an empty value) and cannot reload it (re-assigning the same Uri no-ops) — use GET /api/source/clear?target=<id> and GET /api/source/reload?target=<id> for those. Browse selectable files with the /api/files/* endpoints. Render as a path readout with Load / Reload / Clear actions.
  • color — an AdvancedColorPicker (RGBA colour surface). Live value is the serialized picker: { "Owner": {Id,Name,Type}, "RedProperty", "GreenProperty", "BlueProperty", "AlphaProperty", "ColorString", "ColorState", "ShowAlpha" }. RedProperty/GreenProperty/BlueProperty/AlphaProperty name the live 0–255 int channel properties on the owning component — read those from the same getproperties response for the current colour (authoritative). ColorString is a "R G B A" fallback. ShowAlpha is false when the picker has no alpha channel. Render as a colour rectangle; either composite alpha over a checkerboard, or fill with solid RGB and surface R/G/B/A + hex on hover so the colour stays visible even at alpha 0.

Enum properties: enumValues lists the member names (use these as the value= in /api/setproperty). enumLabels is an index-aligned array of human-readable labels taken from each member's [Description("...")] attribute, falling back to the member name when no description is set. UIs should display enumLabels[i] while sending enumValues[i] as the wire value.

isHidden: true when the property carries either the [Hide] or the [AlwaysHide] attribute. The two attributes mean different things inside Composer Desktop (conditional vs. unconditional hiding across various views), but for HTTP builder UIs they're equivalent — either one says "don't render". Note that this is distinct from isDebug / isDeveloper (which are typically hidden by default but can be revealed by a UI debug toggle).

Nested objects (non-null only when the corresponding attribute is present):

  • slider{ min, max, centerMark } from [EditorSlider]
  • switchLabel{ off, on } from [SwitchLabel]
  • expandableSection{ defaultExpanded } from [ExpandableSection]
  • confirmationRequired{ title, message } from [ConfirmationRequired]
  • requiredCondition{ whenProperty, whenValue } from conditional [Required]
  • fileExtensions{ extension, extension2, description } from [FileExtension]

Error responses:

  • 400 Bad Request — Missing type parameter
  • 404 Not Found — No matching type found

All Builder API endpoints accept GET requests, return JSON, and follow a common response convention:

  • Success200 OK with a JSON body. Create/delete/reorder operations return { success: true, ... }; list operations return a JSON array.
  • Bad request400 Bad Request with a plain-text reason: missing required parameter, unknown type / scene / layer / etc., index out of range, circular scene reference, or input not deletable.
  • Server error500 Internal Server Error with the exception message when something throws unexpectedly.

State changes are dispatched onto the UI thread synchronously, so the response only returns after the change has been applied.

Input CRUD

GET /api/input/create

Creates a new input by type name. The new input is added to the project root and assigned the proposed name (or an auto-generated unique name if name is omitted or collides).

Parameters:

Parameter Required Description
type Yes Input type simple name, case-insensitive (e.g. RtmpInput, Switcher, MediaFileInput). Use /api/types/inputs to list available types. Scene is rejected — use /api/scene/create instead.
name No Proposed display name. The project's normal de-duplication runs against existing names.

Response:

  • 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<assigned name>", "type": "<TypeName>" }
  • 400 Bad Request — Missing type, unknown type, or type=Scene.
  • 500 Internal Server Error — Could not instantiate the type, or unexpected exception.

GET /api/input/delete

Deletes an input by name. The input's connectors are cleaned up automatically (any API commands that referenced it are removed).

Parameters:

Parameter Required Description
targetname (or name) Yes Name of the input to delete.

Response:

  • 200 OK — JSON: { "success": true, "message": "Input '<name>' deleted" }
  • 400 Bad Request — Missing parameter, input not found, or input cannot be deleted (some built-ins are protected).

GET /api/input/list

Lists every input on the project, optionally filtered by type. Excludes scenes — use /api/scene/list for those.

Parameters:

Parameter Required Description
type No Filter by simple type name (case-insensitive).

Response: 200 OK — JSON array:

[
  { "Id": "<guid>", "Name": "RTMP Input 1", "Type": "RtmpInput" },
  { "Id": "<guid>", "Name": "Studio Cam", "Type": "BlackmagicCaptureV3" }
]

Scene CRUD

GET /api/scene/create

Creates a new scene. Width and height are optional — when supplied (either or both), the scene's resolution is set to Custom; otherwise the project default is used.

Parameters:

Parameter Required Description
name No Proposed scene name.
width No Custom output width in pixels.
height No Custom output height in pixels.

Response: 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<assigned name>", "type": "Scene" }


GET /api/scene/delete

Deletes a scene by name. Connector API commands referencing the scene are removed automatically.

Parameters:

Parameter Required Description
scene (or name) Yes Scene name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Scene '<name>' deleted" }
  • 400 Bad Request — Missing parameter or scene not found.

GET /api/scene/list

Lists every scene with layer and target counts.

Parameters: None

Response: 200 OK — JSON array:

[
  { "Id": "<guid>", "Name": "Front Camera", "LayerCount": 4, "TargetCount": 1 },
  { "Id": "<guid>", "Name": "PGM out", "LayerCount": 2, "TargetCount": 3 }
]

GET /api/scene/duplicate

Duplicates a scene including all of its layers (with their operator stacks) and targets.

Parameters:

Parameter Required Description
scene (or name) Yes Source scene name.
newname No Name for the duplicate. If omitted, the project's normal de-duplication generates one (<name> (1), etc.).

Response:

  • 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<new name>", "type": "Scene" }
  • 400 Bad Request — Missing parameter or source scene not found.

GET /api/scene/setactive

Sets the active scene by name. The active scene is the one shown in the preview window and selected by default for cross-scene operations.

Parameters:

Parameter Required Description
scene (or name) Yes Scene name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Active scene set to '<name>'" }
  • 400 Bad Request — Missing parameter or scene not found.

Layer CRUD

GET /api/layer/create

Creates a new layer in a scene from an existing input. Initial transform and layer properties can be set inline using transform.* and layer.* query-parameter prefixes.

Parameters:

Parameter Required Description
scene Yes Scene name to add the layer to.
source Yes Name of the input that backs the layer. The input must already exist.
name No Proposed layer display name.
layer.<PropertyName> No Set a layer-level property at creation time. Example: layer.IsVisible=true, layer.Opacity=50.
transform.<PropertyName> No Set a transform property. Example: transform.PositionX=100, transform.ScaleX=0.5.

Response:

  • 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<name>", "type": "Layer", "layerTransformId": "<guid>", "layerAudioId": "<guid>" }. Use the transform / audio IDs as targets for subsequent /api/setproperty calls.
  • 400 Bad Request — Missing parameters, scene or source not found, or circular scene reference (the source scene is upstream of the destination scene).

GET /api/layer/delete

Deletes a layer from a scene.

Parameters:

Parameter Required Description
scene Yes Scene name.
layer (or name) Yes Layer name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Layer '<name>' deleted from scene '<scene>'" }
  • 400 Bad Request — Missing parameter, scene not found, or layer not found in scene.

GET /api/layer/list

Lists every layer in a scene including the linked transform and audio component IDs (handy for following up with /api/setproperty calls).

Parameters:

Parameter Required Description
scene Yes Scene name.

Response: 200 OK — JSON array:

[
  {
    "Id": "<guid>",
    "Name": "Camera 1",
    "InputName": "Studio Cam A",
    "InputType": "BlackmagicCaptureV3",
    "Index": 0,
    "LayerTransformId": "<guid>",
    "LayerAudioId": "<guid>"
  }
]

GET /api/layer/reorder

Moves a layer to a specific Z-order index in its scene. Index 0 is the bottom of the stack (rendered first).

Parameters:

Parameter Required Description
scene Yes Scene name.
layer Yes Layer name.
index Yes Target index (integer).

Response:

  • 200 OK — JSON: { "success": true, "message": "Layer '<name>' moved to index <i>" }
  • 400 Bad Request — Missing parameter, non-integer index, scene or layer not found.

Target CRUD

GET /api/target/create

Creates a new target in a scene by type name.

Parameters:

Parameter Required Description
scene Yes Scene to attach the target to.
type Yes Target type simple name, case-insensitive (e.g. RtmpTarget, SrtTarget, FileRecorderTarget). Use /api/types/targets to list available types.
name No Proposed display name.

Response:

  • 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<name>", "type": "<TypeName>" }
  • 400 Bad Request — Missing parameter, scene not found, or unknown target type.
  • 500 Internal Server Error — Could not instantiate the type.

GET /api/target/delete

Deletes a target from a scene.

Parameters:

Parameter Required Description
scene Yes Scene name.
target (or name) Yes Target name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Target '<name>' deleted from scene '<scene>'" }
  • 400 Bad Request — Missing parameter, scene not found, or target not found in scene.

GET /api/target/list

Lists every target attached to a scene.

Parameters:

Parameter Required Description
scene Yes Scene name.

Response: 200 OK — JSON array:

[
  { "Id": "<guid>", "Name": "RTMP Output 1", "Type": "RtmpTarget" }
]

GET /api/target/reorder

Moves a target to a specific index in the scene's target list. Order is cosmetic — every target receives the same composited frame on the same render tick.

Parameters:

Parameter Required Description
scene Yes Scene name.
target Yes Target name.
index Yes Target index (integer).

Response:

  • 200 OK — JSON: { "success": true, "message": "Target '<name>' moved to index <i>" }
  • 400 Bad Request — Missing parameter, non-integer index, scene or target not found.

Operator CRUD

GET /api/operator/create

Creates a new operator on a layer. Most operator types use a parameterless constructor, but a handful expose a static factory method instead — both forms are supported.

Parameters:

Parameter Required Description
scene Yes Scene name.
layer Yes Layer name.
type Yes Operator type simple name (e.g. BlurOperator, HsvKeyer, ColorAdjust). Use /api/types/operators to list available types.
name No Proposed display name.

Response:

  • 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<name>", "type": "<TypeName>" }
  • 400 Bad Request — Missing parameter, scene / layer not found, or unknown operator type.
  • 500 Internal Server Error — Could not instantiate the type.

GET /api/operator/delete

Deletes an operator from a layer.

Parameters:

Parameter Required Description
scene Yes Scene name.
layer Yes Layer name.
operator (or name) Yes Operator name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Operator '<name>' deleted from layer '<layer>'" }
  • 400 Bad Request — Missing parameter, scene / layer / operator not found.

GET /api/operator/list

Lists every operator on a layer in execution order (top of the stack at index 0).

Parameters:

Parameter Required Description
scene Yes Scene name.
layer Yes Layer name.

Response: 200 OK — JSON array:

[
  { "Id": "<guid>", "Name": "Color correction", "Type": "ColorAdjust", "Index": 0 },
  { "Id": "<guid>", "Name": "Blur", "Type": "BlurOperator", "Index": 1 }
]

GET /api/operator/reorder

Moves an operator to a specific index in the layer's stack. Operator order matters — they process top-to-bottom.

Parameters:

Parameter Required Description
scene Yes Scene name.
layer Yes Layer name.
operator Yes Operator name.
index Yes Target index (integer).

Response:

  • 200 OK — JSON: { "success": true, "message": "Operator '<name>' moved to index <i>" }
  • 400 Bad Request — Missing parameter, non-integer index, scene / layer / operator not found.

Connector CRUD

GET /api/connector/create

Creates a new API connector at the project level. Empty connectors have no commands and no effect when triggered — add commands with /api/connector/command/add.

Parameters:

Parameter Required Description
name No Proposed connector display name.
description No Free-form description.

Response: 200 OK — JSON: { "success": true, "id": "<guid>", "name": "<name>", "type": "ConnectorTrigger" }


GET /api/connector/delete

Deletes an API connector by name.

Parameters:

Parameter Required Description
name Yes Connector name.

Response:

  • 200 OK — JSON: { "success": true, "message": "Connector '<name>' deleted" }
  • 400 Bad Request — Missing parameter or connector not found.

API Command CRUD (within Connectors)

API Commands are the individual property-set / command-invoke actions inside a connector. They run in order each time the connector is triggered.

GET /api/connector/command/add

Adds a new API Command to an existing connector. The command's target can be any named project entity — input, scene, layer, layer transform, layer audio, operator, or target.

Parameters:

Parameter Required Description
connector Yes Connector name.
targetname Yes Target entity name or GUID. GUIDs are resolved first; names second. Useful for targeting layer transforms / layer audios by ID since they don't have user-visible names.
property Yes Property name to set on the target (case-insensitive). Must be a write-enabled property.
value No Value to assign. Type-converted to the property's type via TypeDescriptor; falls back to the raw string if conversion fails.
animationtime No Animation duration in milliseconds (integer). When set on an animatable property, the value is interpolated over this duration on connector trigger.

Response:

  • 200 OK — JSON: { "success": true, "message": "Command added to connector '<name>'" }
  • 400 Bad Request — Missing parameter, connector not found, or target not found.

GET /api/connector/command/delete

Deletes an API Command from a connector by index. Use /api/connector/command/list to find the index.

Parameters:

Parameter Required Description
connector Yes Connector name.
index Yes Zero-based index in the connector's command list.

Response:

  • 200 OK — JSON: { "success": true, "message": "Command at index <i> deleted from connector '<name>'" }
  • 400 Bad Request — Missing parameter, non-integer index, connector not found, or index out of range (0 to count - 1).

GET /api/connector/command/list

Lists every API Command in a connector, in execution order.

Parameters:

Parameter Required Description
connector Yes Connector name.

Response: 200 OK — JSON array:

[
  {
    "Index": 0,
    "TargetName": "Camera 1",
    "TargetType": "Layer",
    "Property": "IsVisible",
    "Value": "true",
    "IsActive": true
  },
  {
    "Index": 1,
    "TargetName": "Camera 2",
    "TargetType": "Layer",
    "Property": "IsVisible",
    "Value": "false",
    "IsActive": true
  }
]

IsActive reflects the per-command enable / disable checkbox in the Connectors UI.