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
EnableBuilderApioff 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
EnableBuilderApito betruein 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
EnableBuilderApioff 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
RequiredPluginClassNamehook. 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: truecan 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 theRequiredPluginClassNamehook and perform their own internal license checks; they reportRequiresPlugin: ""/PluginLicensed: truehere 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 —PluginLicensedreports 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/PluginLicensedfields 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/PluginLicensedcarry 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:
description—MultilineTextparagraph used byAboutDescription. Live value is{ "Text": "..." }.externalLink—ExternalUrlLinkused byExternalLinkfor clickable documentation links. Live value is{ "HttpLinkString", "LinkText", "ToolTip" }.commandGroup—CommandGrouprendered as a horizontal button strip of 2–4 commands. The descriptor includes acommandsarray — index-aligned with the group's buttons — where each entry is{ "propertyName", "displayName", "tooltip" }. UsepropertyNameas thecommandparameter to/api/invokecommandso the button on click invokes the right top-level Command property. Live values for the group are the serialized CommandGroup (itsCommands[]array exposes each command'sNameandCanExecute).formattedMessage— a single status line. Live value is{ "Message", "DateTime", "FormattedMessageType" }whereFormattedMessageTypeisInfo/Warning/Error. Render as one severity-tinted line, no label.componentLog— a bounded ring buffer ofFormattedMessageentries. 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 asselected:opt1/opt2/opt3— the server-sideStringCollectionEnumConverterparses this and replaces the property with a new instance.propertyNote— a hint/note box. Live value is{ "IsVisible", "Icon", "Text", "LinkUrl", "LinkText", "MarginTop", "MarginBottom" }.Iconis one ofNone,Info,Tip,Warning,Error,Success. Render only whenIsVisibleis true; styling should reflect the icon kind.dictionary— anyIDictionary(typicallyDictionary<string, object>), used for metadata bags likeMoQInput.VideoStreamInfo/AudioStreamInfo. Live value is a JSON object with arbitrary keys; render as a key/value table.connectionStatus— theConnectionStatusenum (Disconnected/Connecting/Connected/Reconnecting). Distinct from genericenumso clients can match Composer Desktop's status-pill rendering (green dashed border whenConnected, etc.) instead of treating it as a dropdown. Live value is the enum name as a string.fileAsset— aUri-typed file/URL source (e.g.StillImageInput/MediaFileInputSourceUrl, decorated[FileAsset]). Live value is theUriserialized as its string (empty/absent when no source). Set a new source with/api/setproperty(value=<path-or-url>);/api/setpropertycannot clear it (it rejects an empty value) and cannot reload it (re-assigning the same Uri no-ops) — useGET /api/source/clear?target=<id>andGET /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— anAdvancedColorPicker(RGBA colour surface). Live value is the serialized picker:{ "Owner": {Id,Name,Type}, "RedProperty", "GreenProperty", "BlueProperty", "AlphaProperty", "ColorString", "ColorState", "ShowAlpha" }.RedProperty/GreenProperty/BlueProperty/AlphaPropertyname the live 0–255intchannel properties on the owning component — read those from the samegetpropertiesresponse for the current colour (authoritative).ColorStringis a"R G B A"fallback.ShowAlphais 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— Missingtypeparameter404 Not Found— No matching type found
All Builder API endpoints accept GET requests, return JSON, and follow a common response convention:
- Success —
200 OKwith a JSON body. Create/delete/reorder operations return{ success: true, ... }; list operations return a JSON array. - Bad request —
400 Bad Requestwith a plain-text reason: missing required parameter, unknown type / scene / layer / etc., index out of range, circular scene reference, or input not deletable. - Server error —
500 Internal Server Errorwith 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— Missingtype, unknown type, ortype=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/setpropertycalls.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 (0tocount - 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.