# Welcome

Welcome to the NewBlue Developer Portal. The following pages showcase everything Captivate's API is capable of.

Our API empowers you to effortlessly connect Captivate with your existing systems, enabling real-time data-driven visuals, seamless control, and automated workflows. Whether you're enhancing live broadcasts, simplifying tasks, or integrating external data sources, Captivate’s API is designed to make your production process faster, smarter, and more efficient.

Dive in and discover how easy it is to elevate your live graphics experience!


# Introduction

Reference and Examples

NewBlue Captivate offers a powerful, easy, and accessible API for driving title graphics. This API is exposed over multiple network connection methods and can be utilized by anything that can communicate over a network.

Using this API, you can write scripted “data controllers” that parse incoming data from any imaginable source and use it to update live titles, control their playback, and much more.

These controllers are often written as small, self-contained web pages using HTML and JavaScript and communicate with the Captivate API over a WebSocket connection. In fact, many of our own built-in controllers are developed that same way, and we provide JavaScript libraries to manage that exact workflow.

<figure><img src="/files/IXG7EGwHXiMFK4r6J4k6" alt=""><figcaption></figcaption></figure>

### OpenAPI Spec

Additionally, our API can be accessed using REST style HTTP requests, direct TCP connections, and more. The attached OpenAPI specification documents the entire API according to the HTTP method and has instructions for how to translate those API calls to the other connection methods as well.

Download our spec here:

{% file src="/files/QtCUCNFgpSmrzNpGPNiZ" %}
OpenAPI Captivate Specification
{% endfile %}

## API Explorer

Also try out our interactive [API explorer](https://newbluefx.com/api/explorer).


# Terminology

A quick summary of the key components in this system follows:

* Titles
* Controllers
* Inputs
* Variables
* Scheduler

## Titles

Titles are individual graphic designs that display text and other visual information. As such, the term “graphic” is sometimes used to refer to a title.

Multiple titles can be displayed at the same time and they will naturally layer one on top of the other. Items that are higher in the project list will display on top of lower items, and therefore, titles can sometimes be referred to as “layers.”

Finally, the project list can contain items that are not titles per se. Transitions, workflow controllers, media items, and more may show up in the project list.

Nevertheless, the API consistently uses the term Title in reference to any project item that is "playable," whether that is a graphical title, a media sequence, or something else.

## Controllers

Controllers, also called “data controllers” or “service handlers,” are custom data drivers that connect with specific title graphics and control what to display in the title fields, from text and images to colors and even bar graphs.

The controller might read data from a source or create the data itself. It then sends the data as variables to the scheduler, which routes the data to the appropriate title graphics.

For example:

* A controller for a basketball scoreboard might read live score and clock data from a hardware device and convert it into the appropriate text updates for a scoreboard title.
* A weather controller might read current temperature, barometric pressure, city name and photograph to send to a weather title.
* A track and field controller might offer a web interface that allows a user to key in names and results to then display a leaderboard title.

## Inputs

Each controller represents itself to the system with one or more “inputs.” They are called inputs because they ingest data from an outside source and bring it into Captivate to be used by a title . Therefore, an input represents a specific connection between external data and a title for a specific purpose.

For example, a controller that reads and displays stock prices might have two inputs that offer different kinds of data:

1. The first is a set of data about any specific stock that presents via a card title graphic.
2. The second is a constantly running list of stock values, perhaps to be played as a crawl across the screen bottom.

## Variables <a href="#variables" id="variables"></a>

Variables refer to the actual data values that flow from a controller’s input through the scheduler to a specific title for visual display. Variables come in several data types, with the most frequently used being text, image, and color.

The variable system makes it possible for one title to display different values throughout a production without needing to create multiple similar titles.

For example, a lower third design might be used to show the names of different people in a program. Instead of having one title for each person, just one title is prepared and the controller driving it sends the different names via variable values.

## Scheduler

The scheduler is the core of the API and it exposes nearly all functions that can be done in the Captivate user interface. The scheduler manages playback of titles, updating variable values for inputs, and much more.


# Captivate Controller Setup

Many of Captivate's built-in controllers use JavaScript embedded in HTML pages to drive data to the application. Captivate's embedded browser hosts the HTML page as a user interface to the controller and the JavaScript code connects to the Captivate WebSocket API to schedule data and playback operations on titles.

Using this method, any data that can be entered by a user, accessed from the web, or read from a file can be pulled into Captivate, translated into variables by JavaScript, and sent back to the Captivate scheduler.

For convenience, Captivate includes a built-in HTTP server to optionally host your HTML pages, making it even easier to create your own simple controllers.

> **Note:** For definitions of the different terms, controller, input, and title, please see the [Terminology](/getting-started/terminology) page. For detailed explanation of our API, please see the [Controller API Reference Page.](/captivate-api/javascript-api-reference)


# Setup Your Folders

## Make a Folder for your Controller

Pre-installed controllers live in these folders:

* On Windows: `C:\Program Files\NewBlueFX\Titler Content\Resources\Service Handlers\HTML`
* On Mac: `/Library/Application Support/NewBlue/Titler Content/Resources/Service Handlers/HTML`

You may inspect our pre-installed controllers there and you can put your own controller folder there, but you can also install your controller folder in one of our “Shared” controller locations:

* On Windows: `C:/ProgramData/NewBlue/LiveEngine/DataControllers`
* On Mac: `/Users/Shared/NewBlue/LiveEngine/DataControllers`

If the same controller is defined in both locations, the one with the higher `version` attribute will be preferred.

Navigate to one of the controller folders and create a new folder. The folder name will determine where your inputs show up in the **Data Controller** menu in Captivate. In this example, `Scoreboard Tool` is the name of the folder and `Baseball`, `Football`, etc. are the names of the inputs defined by that controller.

<figure><img src="/files/0GgGTjxGCcM5uXoT4iyZ" alt=""><figcaption></figcaption></figure>

There can be many input definitions for each controller, each one defined by a different XML file in that folder.

## Understand the Controller File Structure

A controller can have multiple inputs. Each input is defined by an XML file in your controller’s folder. The XML document will define the input’s name and metadata, its initial variables along with their types and formats, and most importantly, the url to the HTML page where the user interface and JavaScript code lives.

Captivate reads all controller XML files only once when it launches, so even though you can change the HTML and JavaScript anytime, if you change what’s in the XML file, you will need to restart Captivate to see the changes.

With your controller, you should also supply an icon. This icon should be identified in the XML file and should live right next to it in the file system. The icon will be displayed in Captivate next to the name of this input whenever it is used.

If the HTML file is to be served by Captivate’s built-in server, then it should be placed, with all related files, in the same folder as the XML definition. It could also be placed in a subfolder of that folder. These files will be hosted by the built-in web server and will be displayed in an embedded browser in the Captivate program.

Alternatively, you can host your Controller files anywhere on your local network or even on the public Internet. Just make sure your XML file accurately describes the url to the main HTML page for your input.

> **Note:** The Captivate web server operates over insecure HTTP, but Captivate displays controllers using a modern browser engine with most security features enabled. As a result, any JavaScript features that require a secure context (e.g. web workers, javascript modules) will not be available to you when you are using the built-in web server.

Here is the simplest file structure for a controller named `My Controller`.

```
C:/ProgramData/NewBlue/LiveEngine/DataControllers/
    My Controller/
        controller.xml
        controller.html
        controller_icon.png
```

Only the xml file and the png file need to go in the main controller folder. You can put the html file and other assets in any subfolder structure you want. For exmample:

```
C:/ProgramData/NewBlue/LiveEngine/DataControllers/
    My Controller/
        controller.xml
        controller_icon.png
        controller/
            controller.html
            images/
                other_image.png
            css/
                style.css
            js/
                app.js
```


# XML Definition

## Create Your XML File

In the pre-installed controller folder mentioned on the previous page, you will find many other controllers you can use as examples. Here is a detailed description of the data that can go into an Input Definition XML file.

## XML Definition

A single HTML data controller can have several inputs which dictate the variables that are sent to Captivate. Each input needs an XML file to define its properties.

Here is an example of an input behavior:

```xml
<?xml version="1.0" encoding="utf-8" ?>

<inputBehavior
  name="Controller Name: Input Name"
  url="exampleSrc/exampleApp.html"
  iconOn="exampleSrc/alien.png"
  UIGroups="General;Sports"
  version="1.0"
  index="0">
  <variable name="Name" type="text"  />
  <variable name="Logo" type="image"  />
  <variable name="HideLogo" type="visible"  />
  <variable name="Background" type="color"  />
  <variable name="Progress" type="graph"  />
  <variable name="Time" type="text">
    <pattern>[0-2][0-9]:[0-5][0-9][ap]m</pattern>
  </variable>
  <controls>
    <button name="Clear All"
        action="clearAllMessages"
        tooltip="Delete all messages from the database" />
  </controls>
</inputBehavior>
```

> **Important**: The XML files must be UTF-8 encoded.

### Input Behavior

The `<inputBehavior>` tag defines the start and end of the input behavior definition.

```xml
<inputBehavior
  name="Folder Name: Input Name. Sub Controller"
  version="1.0"
  url="exampleSrc/exampleApp.html"
  iconOn="exampleSrc/alien.png"
  UIGroups="General;Sports"
  family="Controller Name"
  index="0"
  hasHelpPanel="true"
  hasDataVariablePanel="true"
  InheritUI="false"
  uiName="Folder Name: Input Name">
  <!-- variables and controls go here -->
</inputBehavior>
```

Parameters:

* `name` - The name by which this input will be referenced in the UI and in all API calls. This must be unique among all HTML-based controllers since all HTML controllers share the same server.
  * Names should be formatted as `"Folder Name: Input Name"`. The `Folder Name` portion is sometimes referred to as the Family Name, Service Name, or Controller Name, but it must exactly match the name of the folder on disk where the XML file is found. The rest of the name determines how this controller’s input will be displayed in the data controllers submenus. *See the notes below.*
  * Periods (“.”) and colons (“:”) can be used to create more subtrees in the UI. For example, if a controller is named `"Weather Input: Temperature Inputs. 7 Day Temperature Forecast"` and it has no `UIGroups` attribute, it will appear in the data controllers dropdown menu as:
    * `General`
      * `Weather Input`
        * `Temperature Inputs`
          * `7 Day Temperature Forecast`
  * *Note:* If a controller name has *no* colon (`:`), it will show up twice in the controllers dropdown menu. e.g. `Name` > `Name`
  * *Note 2:* If a controller name *has* a colon (`:`), the text to the left of the colon must match the folder name where its XML file is stored or else the controller’s settings might fail to save properly when the project is saved.
* `version` - The version helps Captivate determine which to use if two controllers have the same `name`. By using a higher version number, a controller in the shared controllers location can override the controller found in the pre-installed location.
* `url` - The path to the HTML page used to process this input. If this is running in the embedded server, then it should be relative to the XML file. If this is running on an external server, then this should be a fully-formed url, i.e. `"https://example.com/example_folder/mycontroller.html"`
* `iconOn` - the icon used for this controller in the UI. This should also be a path relative to the XML file and should point to a square png image file.
* `UIGroups` - (optional) A semicolon-separated list of the UI group(s) under which this input will appear. If you leave it out, it will default to “General.” Here are some standard groupings that may be included in your edition of Captivate:
  * `"API"` *(Programming Examples)*
  * `"General"`*(General Purpose)*
  * `"Sports"`*(Scoreboards, Stats, etc.)*
  * `"Gaming"`*(Twitch, Streamlabs…)*
  * `"Productivity"`*(Spreadsheet, Slides, etc.)*
  * `"Social"`*(Social Media)*
  * `"Worship"` *(House of Worship)*
* `InheritUI` - (optional) - If you plan to use the *same HTML file* for more than one input, set this to “true” to ensure that when switching between inputs from the same family, the same browser instance and page will be used without a reload. In this way, the same JavaScript context can be preserved among multiple inputs, but your HTML file and JavaScript code will have to be written to understand the different inputs and not be confused by them.
* `uiName` - (optional) - When using the same URL for multiple inputs, set this field. All inputs that share the same `uiName` will use the same URL. Note: if multiple XML files specify different urls but the same `uiName` the url from the first-parsed XML file will be used.
* `helpUrl` - The path to an HTML page of help text.
* `hasHelpPanel` - If this is set, Captivate will expose a help (`?`) button. When pressed, it will call the global JavaScript function `showControllerHelp()` in the embedded browser. You must implement that function yourself. (Planned for the future. Not enabled yet.)
* `hasDataVariablePanel` - If this is set, Captivate will expose a `DATA` button that when pressed will call the global JavaScript function `showControllerVariables()` in the embedded browser. You must implement that function in the global scope. (Planned for the future. Not enabled yet.)

**Notes**

> You might see other attributes in the `<inputBehavior>` tag. These are used by **NewBlue** for internal purposes. They may be safely ignored and won’t be of any use in a custom controller. Specifically, you might see any of the following: `family`, `index`, `qaUrl`, `betaUrl`, `requiresNodeJs`, `wantsUniqueNodeJsInstance`, and `nodeJsClientUrlPath`.

> Although it’s possible to load controller UI from the web, if the controller is loaded from an HTTPS server you might receive Mixed Content warnings. This is because all communication with Captivate happens over an unsecured websocket connection and browsers don’t like mixing secure content with insecure content. Our current `ServiceHandler` library gets around this by using WebRTC connections and/or tunneled connections through a secure proxy if it can. See the Controller API Reference for more on that.

#### Control Definitions

`<control>` elements allow you to create buttons in the main Captivate UI that will call functions in the JavaScript context. This works exactly like `hasDataVariablePanel` and `hasHelpPanel`. The buttons will be placed at the bottom of the data controller panel next to the `Open in Browser` button. When the user clicks on the button, Captivate will attempt to call a JavaScript function identified by the `action` attribute in the context of the controller’s global JavaScript environment.

For example:

```xml
<controls>
  <button name="Clear All"
      action="clearAllMessages"
      tooltip="Delete all messages from the database"
  />
</controls>
```

When the user clicks on the “Clear All” button, Captivate will attempt to call `window.clearAllMessages()` in that controller’s JavaScript environment.

#### Variable Definitions

`<variable>` elements describe the names and types of the variables which the input will send to Captivate.

Example:

```xml
<variable name="Name" type="text" />
<variable name="Logo" type="image" />
<variable name="HideLogo" type="visible" />
<variable name="Background" type="color">
  <value>#ff00ff00</value>
  <value>#ff0000ff</value>
</variable>
<variable name="Progress" type="graph" />
<variable name="Time" type="">
  <pattern>[0-2][0-9]:[0-5][0-9][ap]m</pattern>
</variable>
<variable name="Time 2" type="" pattern="[0-2][0-9]:[0-5][0-9][ap]m" />
```

**Variable Attributes:**

* `name` - the variable’s name by which it will be referenced in the UI and JavaScript calls. If you put a `.` in the variable name, it will display in the `Link Data` panel as an expandable tree format. This allows you to categorize your variables.
  * For example: `Day1.Temperature` and `Day1.WindSpeed` will show up as `Temperature` and `WindSpeed` under the expandable `Day1` item, but you still need to use the full name when updating their values. (This behavior doesn’t happen if the `.` is the last character in the variable name.)
* `type` - (optional) attribute specifies the variable type. This declaration works like a filter. When specified, Captivate will only allow this variable to connect to graphic variables that match the type. When omitted or when specified as the empty string, Captivate will allow the variable to connect to any graphic variable regardless of type. Current options include:
  * `text` - Text. Captivate will only allow variables of this type to connect to graphic text boxes.
  * `visible` - Controls graphic visibility. Captivate will allow this variable to connect to “visibility” variables in the graphics.
  * `image` - File path or URL to an image (`jpg`, `png`, `tiff`). Captivate will expect this variable to contain the *full file path with extension*.
    * *Note:* Some servers on the Internet identify images by sending image type data in Header fields only and older versions of Captivate will not be able to use those images directly. If the url to the image doesn’t work, first download them locally. See the [`getFileContent` ](/captivate-api/all-commands-reference/file-tools#api-command-getfilecontent)command in the All Commands documentation.
  * `graph` - Captivate will expect this variable to contain a string representing a value between 0 and 1 (with a minimum step size of 0.001) and will use it to drive a data graph field in a title. Example values will be `0`, `0.5`, `0.9` `1`
  * `color` - Captivate will expect this variable to contain a string value describing a color in `#AARRGGBB` format. A fully transparent color will start with `#00...` and a fully opaque color will start with `#FF...`. A partially transparent green will be `#A000FF00`.
  * *Note:* in most cases, you should just leave the `type` value out. An untyped variable can be linked to anything in the graphic and provides the most flexibility for connecting your controller to different graphic designs.
* `pattern` - (optional) specifies the pattern Captivate will use to pre-render possible values. See more on the next page.

**Notes:**

> When an input sends variable values to Captivate, they should always be sent as strings. When using the JavaScript API, numbers will be automatically converted to strings. Since that behavior can be unpredictable, we recommend sending everything as a string.

> When an input first loads, Captivate will automatically match variables defined by the controller to variables defined in the graphic if they share the same name.

> When a graphic exposes a “visibility” variable, Captivate will display it in the UI of the Properties panel as a checkbox. Internally, these variables are represented as strings that are either empty for `false` or non-empty for `true`. In other words, to turn a visibility variable on, send any non-empty string value (`'1'`), and to turn it off, send the empty string (`''`). This technique allows the visibility of a graphic to be controlled by any variable, not just those with type `visible`.

#### HTML <a href="#html" id="html"></a>

If the HTML file is to be served by Captivate’s built-in server, then it should be placed, with all related files, in the same folder as the XML definition or optionally in a subfolder of that folder.

See [Setup Your Folders](https://github.com/NewBlueFX/gitbook-repo/blob/main/docs/captivate-api/captivate-controller-reference/setup-your-folders/README.md)


# Pattern Variables

The optional `pattern` attribute (we also support a `<pattern>` child node which takes precedence over the `pattern` attribute) defines a pattern. Captivate can use the pattern to pre-render possible values before they are needed, improving performance and allowing each digit to animate independently from the whole.

This is especially useful for clocks and scoreboards, which usually stay in the same format for the duration of a broadcast or video and need low-latency, real-time updating and thus have less time to render.

> **Note:** If a *graphic* defines a pattern, the pattern defined by the controller will take precedence.

The following is an example of a pattern for a 12-hour hour:minute clock with am/pm at the end.

```xml
<pattern>1[0-9]:[0-5][0-9][ap]m</pattern>
```

This is how you build a pattern:

| Pattern | Interpretation                                         |
| ------- | ------------------------------------------------------ |
| `[]`    | denotes a character group                              |
| `-`     | inside a character group denotes a range of characters |
| `\`     | escape the next character and treat it as literal      |
| any     | all other characters are treated as literal            |
|         | don't use spaces in a pattern, spaces are always valid |

> **Note**: Variables with patterns specified are sometimes referred to as pattern variables.

How Captivate translates variable data to fit the pattern:

* When Captivate defines a pattern on a variable, it creates a format string for that pattern. The format string is generated by each literal character and the first character from each character group. In the pattern given above, the format string will be `10:00am`.
* Captivate measures the rendered format string to calculate a “cell” on screen for each character, and generates animations for all possible values of each cell.
* When Captivate receives a value for the variable, it tries to fit the new data into the proper cells based on how well the new data fits the format string.
* After Captivate computes the best match, any characters that aren't allowed for a cell will be replaced by a space character `" "` instead of whatever was in the related input value.
* By default, the process of finding a match will result in numeric patterns being right-justified and non-numeric patterns being left-justified. Examples will follow.
* To avoid surprises, you can send Captivate data that exactly matches the pattern in question, adding space characters intentionally where needed. Again, see the examples below.

**Notes:**

> Space characters are always considered a possibility for every cell and should not be specified in the pattern string. If your final output requires a space character, set your pattern to use a different character of the proper width in that position.

> If a variable pattern needs to include `<`, `&` or `>` (characters reserved by XML format), use a `<![CDATA[]]>` section. Here’s an example to represent *Down and Distance* for American Football (using a `#` and a `.` to manually tweak the spacing):
>
> ```xml
> <pattern><![CDATA[ [4321][snrt][dht]#&.[0-9][0-9] ]]></pattern>
> ```

> Captivate generates the width of a cell from the *first* character in each character group. Therefore, if you use a narrow character as the first character in a group, you might be surprised when Captivate tries to fit a wider character into that cell. A pattern like `[1-5]` will create a cell for the width of the `1` and the other values won't easily fit in that cell. Make sure the widest possible character shows up first in your pattern, so for digits from 1-5, you might make the pattern `[51-4]` just to make sure the cell is wide enough to accommodate a `5`.

> Captivate decides whether to use left or right justification based on the *leftmost* character in the format string. If that character is numeric, the pattern will adopt a right-justified scheme to keep numbers right-aligned. Otherwise, the pattern will use a left-justified scheme. For precise positioning of your variable values, see the examples below.

**On-screen alignment (monospace vs. proportional fonts)**

The justification described above decides *which* cells a value fills. Where the filled value then sits on screen is controlled by the **paragraph alignment of the text box** that holds the variable:

* **Left, Right, or Center** alignment — the matched value is aligned or centered as a unit within the pattern's full width. With a **proportional** font the visible characters are spaced proportionally; with a **monospace** font every character stays locked to a fixed grid, so individual digits never shift position as the value changes (no per-digit "jitter").
* **Full Justification** — restores the legacy fixed-cell layout: every cell occupies a fixed position regardless of the value or the font, so each character is pinned to an exact spot. Use this for a classic fixed-cell scoreboard or clock look.

> In earlier versions, pattern variables always used the fixed-cell layout. That behavior is now what you get with Full Justification (or the `UseLegacyMultipartLayout` application setting, which forces it globally regardless of text-box alignment).

**Example 1: Controlling the width of a cell**

To always keep a small space before the am/pm designation of a clock, just use a narrow character in the pattern:

```xml
<pattern>1[0-9]:[0-5][0-9]![ap]m</pattern>
```

Then, in your submitted data, leave a space character where the `!` would be and the on screen display will have a space only as wide as the `!` character.

**Example 2: Two Ways to Left-Justify a Numeric Pattern Variable**

**Numeric Pattern**

```xml
<pattern>[0-9][0-9][0-9][0-9]</pattern>
```

The first item in the pattern is numeric and therefore Captivate will prefer to right-justify new values against this pattern. Consider this chart where underscore (`_`) characters are used to designate space characters:

| Variable Input    | Variable Output                                      |
| ----------------- | ---------------------------------------------------- |
| `"1"`             | `___1` (right-justified `1`)                         |
| `"1___"` (spaces) | `1___` (full width input, manual spacing is honored) |

**Non-numeric Pattern**

```xml
<pattern>i[0-9][0-9][0-9][0-9]</pattern>
```

Since the first character in this pattern is not a number (could be anything), Captivate will default to a left-justification scheme. Remember that all characters are optional.

| Variable Input | Variable Output (spaces shown as underscores)                                |
| -------------- | ---------------------------------------------------------------------------- |
| `"1"`          | `_1___` (small space for the missing `i` followed by left-justified `1`)     |
| `"123"`        | `_123` (same as above)                                                       |
| `"i1"`         | `i1___` (if you use the `i` it will be displayed)                            |
| `"___i"`       | `i____` (best fit, the `i` has to be first)                                  |
| `"____1"`      | `____1` (full width input, manual spacing honored)                           |
| `"____i"`      | `_____` (full width input, manual data honored, but no characters are valid) |

**Example 3: Our Standard Clock Pattern**

In most of our sports controllers, we use a standard pattern for a clock. This one pattern supports all the following formats and will keep the numbers aligned to the colon/dot if provided:

* `hh:mm`
* `h:mm`
* `mm:ss`
* `m:ss`
* `ss.t` (tenths)
* `s.t` (tenths)
* `ss.hh` (hundredths)
* `s.hh` (hundredths)

```xml
<pattern>[0-9][0-9][:.][0-9][0-9]</pattern>
```

**Example 4: Advanced Specialty Clock**

This creates a clock pattern where every cell is based on the width of the `8` character, and supports both these formats:

* `12:34`
* `123.4`

```xml
<pattern>[80-9][80-9][8:0-9][8.0-9][80-9]</pattern>
```

This pattern defines five individual cells:

* `[80-9]` -- `8` sets the width (`8` is a little narrower than `0` in most fonts), allow all digits from `0`-`9`
* `[80-9]` -- same as above
* `[8:0-9]` -- `8` sets the width, allow `:` and all digits from `0`-`9`
* `[8.0-9]` -- `8` sets the width, allow `.` and all digits from `0`-`9`
* `[80-9]` -- same as above

When the clock variable has receives the following values, it will parse and render them as follows:

| Variable Input | Variable Output (spaces shown as underscores)    |
| -------------- | ------------------------------------------------ |
| `"01:23"`      | `01:23`                                          |
| `"1:23"`       | `_1:23` (missing value populates with space)     |
| `"1:2"`        | `_1:2_` (missing values populate with spaces)    |
| `"01.23"`      | `01_23` (wrong value in column 3 shows as space) |
| `"01.2"`       | `_01.2`                                          |
| `"1.2"`        | `__1.2`                                          |

#### Values

Some graphics allow variables to contain only a predefined set of values. This can also be represented in the XML definition by using `<value>` child nodes. These values will be pre-rendered when the controller is loaded.

**Example:**

```xml
<variable name="Background" type="color">
  <value>#ff00ff00</value>
  <value>#ff0000ff</value>
</variable>
```

As with patterns, these `value` elements can contain `<![CDATA[]]>` sections.

> **Note**: If a *graphic* defines its own set of possible values, that definition will take precedence over the Controller.


# Controller API Reference

## Overview

The Controller API is the primary way of interacting with Captivate from a data controller. If you are building your own data controller, make sure you read the [Controller Setup Page](/captivate-api/captivate-controller-reference) to learn how to set up your input definition with an XML file and where to put your own HTML files and other assets.

JavaScript-based data controllers are usually hosted by an HTML file loaded into a browser (either Captivate’s built in browser, or a local browser on your computer or mobile device).

Your HTML file should provide some form of UI as well as the JavaScript code to make use of our exposed API.

If you want to access Captivate’s API from outside of the context of an HTML file in a browser, first, get familiar with how the Controller API works, and then head over to our [External API Reference.](/captivate-api/advanced-api-reference)


# Loading Required JS Files

HTML/JavaScript Data Controllers communicate with Captivate over a WebSocket connection made possible with two helper library files. Make sure you include these files in your HTML (they are available in the `common` subfolder of the `Service Handlers/HTML` folder).

```html
<!-- When hosted by Captivate -->
<script type="text/javascript" src="../common/js/qwebchannel.js"></script>
<script type="text/javascript" src="../common/js/servicehandler.js"></script>
```

> **Note:** Technically, you don’t need either. Our primary API communicates in plain text over an unsecured websocket connection, but the `qwebchannel.js` script wraps that communication to allow sending type-safe data and to call native methods from JavaScript. `servicehandler.js` is our own custom wrapper for `qwebchannel.js` that manages the websocket connection and provides an easier API.

If you are hosting your HTML page somewhere else, you can also reference these files directly from our website:

```html
<!-- Using our CDN -->
<script src="https://newbluefx.com/api/v3/common/js/qwebchannel.js" type="text/javascript"></script>
<script src="https://newbluefx.com/api/v3/common/js/servicehandler.js" type="text/javascript"></script>
```

If you want to use this library from the context of a JavaScript module, you only need this:

```html
<script type="module">
  // When called as a module like this, the ServiceHandler module loads qwebchannel internally
  import { ServiceHandler } from 'https://newbluefx.com/api/v3/common/mjs/servicehandler.js';
</script>
```

> **Note about Security**: The communication between JavaScript and Captivate happens over a plain-text websocket connection. As a result, if you are hosting your controller on an HTTPS server, most browsers will block connections to Captivate as “insecure” unless the browser running the controller is on the same computer as Captivate. The `ServiceHandler` library will automatically attempt to work around these browser limitations by trying a series of connections in order. See more in the description of the `init` function on the next page.

> **Note about oAuth**: Some providers like Google don’t like doing oAuth from within the context of an embedded browser like the one running inside Captivate. As a result, you might need to open a system browser that will eventually pass data back to Captivate through the API or some other means. To construct a link that will open in the system browser, you can use `browser://` as the scheme, or you can use the `openUrl` command.


# Using the ServiceHandler

`ServiceHandler` is the helper global object for connecting to the Captivate API server.

#### `init` method <a href="#init-method" id="init-method"></a>

Initializes the connection to the Captivate API server.

```javascript
// when you want the ServiceHandler to automatically determine the best connection method, use this call.
ServiceHandler.init();

// when you want to use a specific websocket url, initialize with a call like this.
ServiceHandler.init(serverUrl);

// when you want to use a pre-existing websocket or any other socket connection (useful when calling from nodejs),
// you may pass the socket as the second argument. The socket must implement at least the `onmessage` and `send`
// methods like the native browser JavaScript WebSocket for sending and receiving messages and for closing connections.
ServiceHandler.init(null, socket);
```

**Arguments**

* `serverUrl` - (optional) URL of the web socket server to use for communication with Captivate. When the script is hosted by the built-in Captivate HTTP server, this argument isn’t needed because Captivate will put its own connection information into a cookie served with the main page. However, if you are hosting your HTML page on another server, you might need to specify this value manually. The url should look like this `ws://CAPTIVATE_COMPUTER_IP:CAPTIVATE_PORT`.
  * `CAPTIVATE_COMPUTER_IP`: The hostname or local IP address of the computer running Captivate.
  * `PORT`: The port Captivate exposes for API communication. Captivate defaults to using port `9023` for the WebSocket API.
    * Changing the webSocket port is possible by editing the NewBlue settings in the Windows Registry or the Mac Preferences.
    * Mac: `defaults write com.newblue.captivate 'Generic HTML.webSocketPort' 10002` (from `~/Library/Preferences/com.newblue.captivate.plist`)
    * Windows RegEdit: `\HKEY_CURRENT_USER\SOFTWARE\NewBlue\Captivate\Generic HTML.httpPort`
    * The main HTTP server port can also be read/set using the `Generic HTML.httpPort` key.
  * To avoid hardcoding this value in your scripts, you can also specify the `host` and `port` using a query string in your browser’s url like this: `?ip=127.0.0.1&port=9023`. The `ServiceHandler` object will automatically look for those values if a `serverUrl` is not provided.
* `socket` - When you are running in nodejs or in some other context, you might have a previously negotiated socket compatible with the JavaScript WebSocket interface. You can pass that socket directly to the `ServiceHandler` as the second argument, specifying the first one as null.

**About the automatic connection method**

When `init` is called with no arguments and the `ServiceHandler` is running in a browser context, the library will attempt to connect with Captivate using a number of different connection methods:

1. `ServiceHandler` will gather some default values from the **URL query parameters**:

* It will use `ip` or `hostName` for the websocket domain (defaults to the domain of the browser url).
* It will use `port` as the websocket port (defaults to `9023`).
* It will use `wsTunnel` as the unique identifier for a remote websocket tunnel (see below).
* Captivate will automatically add these query parameters to any data controller it hosts.

2. `ServiceHandler` will attempt to make the following connections:

* First, it will try to connect to `ws://[ipOrHostName]:[port]` (specified in the url)
* Second, it will try to connect to `ws://127.0.0.1:[port]` (always tries localhost)
* Third, it will try to tunnel its websocket connection through NewBlue’s secure websocket tunnel: `wss://controllers.newbluefx.com/tunnel/[wsTunnel]/client`
* Fourth, if the browser has WebRTC capabilities, `ServiceHandler` will use the NewBlue tunnel to negotiate a **direct secure peer-to-peer** connection to Captivate through WebRTC. If a WebRTC connection can be established, it will replace the websocket tunnel connection resulting in a direct connection that doesn’t pass through any NewBlue servers.

**Example:**

```html
<script src="https://newbluefx.com/api/v3/common/js/qwebchannel.js"></script>
<script src="https://newbluefx.com/api/v3/common/js/servicehandler.js"></script>

<script>
  const cleanups = [];
  const cleanup = () => {
    for (const fn of cleanups) {
      fn();
    }
    cleanups = [];
  };

  // Will attempt to auto-connect.
  ServiceHandler.init();

  ServiceHandler.onready = async () => {
    // The `ServiceHandler.scheduler` object is ready now.
    // Note: webRTC connections take longer to set up, so if one
    // ends up being used, this function might be called a second time.
    // Always make sure to set up your signal connections here.
    cleanups.push(ServiceHandler.scheduler.onNotify.connect(console.log));

    // optional, but you should notify the backend who you are.
    const serverStats = await ServiceHandler.scheduler.notifyClientConnected('My Input Name');

    // serverStats will be an object like this (for more detail, use the `getReport` command)
    // {
    //   "buildDate": "Jan 27 2026",
    //   "buildTime": "09:31:17",
    //   "host": "TitlerLive",
    //   "licensed": "true",
    //   "platform": "macos",
    //   "product": "Captivate",
    //   "sku": "SKUTL5BR",
    //   "version": "2025.0.260127"
    // }
  };

  ServiceHandler.onerror = () => {
    // handle connection failure messages
  };

  ServiceHandler.onclose = () => {
    // handle connection close here
    cleanup();
  };
</script>
```

#### `scheduler` property <a href="#scheduler-property" id="scheduler-property"></a>

Scheduler API object. Will be `undefined` until the `ServiceHandler.onready` callback fires.

**Usage:**

```javascript
// The ServiceHandler.scheduler object will exist as soon as the ServiceHandler object exists
// but calling functions on it will output warnings to the console and resolve to error messages
// until Captivate is connected as if you called a non-existent function
const result = await ServiceHandler.scheduler.getCookies();
// {
// 	"success": false,
// 	"error": "Method not found. Either we have lost connection to Captivate, or the installed version doesn't support this API method."
// }
```

#### Using promises instead of callbacks... <a href="#using-promises-instead-of-callbacks" id="using-promises-instead-of-callbacks"></a>

The `scheduler` object exposes all of its functions in a way that allows callbacks or promises, but they operate differently. The callback method is the older method and will always call the callback with the `string` content of the scheduler response.

To provide a callback to any `scheduler` function, simply include it as the last argument of the function.

However, if a callback function is not provided, the `scheduler` will automatically switch to using the `Promise` method and you can use `await` or `.then()` / `.catch()` functions to access the return value.

Finally, if a function is not exported by the current Captivate API, it will not throw an exception, but will instead respond with a failure message as described above.

```javascript
console.log(await ServiceHandler.scheduler.hello());
/*
{
    "success": false,
    "error": "Method not found. Either we have lost connection to Captivate, or the installed version doesn't support this API method."
}
*/
```

#### `serverUrl` property <a href="#serverurl-property" id="serverurl-property"></a>

Websocket server URL currently used to communicate with Captivate (i.e. `ws://localhost:9023`).

**Usage:**

```javascript
ServiceHandler.onready = () => {
  console.info('ServiceHandler connected to', ServiceHandler.serverUrl);
};
```

#### `onready` callback <a href="#onready-callback" id="onready-callback"></a>

Executes when connection to server is established.

**Usage:**

```javascript
ServiceHandler.onready = () => {
  console.info('ServiceHandler connected');
};
```

#### `onclose` callback <a href="#onclose-callback" id="onclose-callback"></a>

User callback to execute when connection to server is terminated.

**Usage:**

```javascript
ServiceHandler.onclose = () => {
  console.info('ServiceHandler disconnected');
};
```

#### `onerror` callback <a href="#onerror-callback" id="onerror-callback"></a>

User callback to execute when an error is encountered trying to communicate with the server.

**Usage:**

```javascript
ServiceHandler.onerror = (e) => {
  console.error('ServiceHandler error', e);
};
```

## Using the ServiceHandler.scheduler Object

All API actions happen with methods defined on the `scheduler` object. The `scheduler` object directly exposes these functions from the `C++` environment to the JavaScript environment; however, only a few methods are relevant for data controller developers and those are documented here.

In each case, whenever a ‘return’ value is described, it will be passed as the sole argument (as a JSON-encoded string) to a callback function or will be the value of the resolved promise if no callback function is supplied. Additional functions for using the scheduler object can be found on the subsequent pages.


# scheduleAction Method

This method is used to control title playback and/or to update variables with new values. The callback will be passed as a JSON-encoded string with the results of the command (and error messages if applicable).

```javascript
// callback version
ServiceHandler.scheduler.scheduleAction(action, inputName, titleId, variables, callback);

// promise version
await ServiceHandler.scheduler.scheduleAction(action, inputName, titleId, variables);
```

* `action` : See below for a description of *some* different possible actions.
* `inputName`: Schedule the action on all titles connected to the input with this name. Use the empty string (`""`) to schedule the action on all titles in the project or when specifying the `titleId`.
* `titleId`: Schedule the action on the one title identified by its internal id value. Use the empty string (`""`) when specifying the `inputName`. The `titleId` value can be retrieved by using API commands (the easiest way to get `titleId` is with the `getTitlesPlayStatus` or the `getTitlesConnected` command, see below).
* `variables`: Basic JavaScript object of variable names and values.
* `callback`: The callback will be passed a **JSON-encoded string** with the results of the command (and error messages if applicable).
* `promise`: If no callback function is given, the function will return a promise that will resolve to a JavaScript object.
  * NOTE: Promises will automatically parse the API response to an object, but callbacks return an unparsed string.

An action is usually one of the following presets (**capitalization doesn’t matter**):

| Action        | Description                                                                           |
| ------------- | ------------------------------------------------------------------------------------- |
| `animateIn`   | Start at the beginning and play up to the pause point.                                |
| `animateOut`  | Start at the pause point and play out.                                                |
| `alert`       | Play the title in, hold for duration, then play out.                                  |
| `cutIn`       | Start playback in the middle and hold.                                                |
| `cutOut`      | Stop immediately.                                                                     |
| `render`      | Render the selected variable(s) so they are ready to play.                            |
| `update`      | Update the selected variable(s) using animations.                                     |
| `still`       | Update the selected variable(s) instantly.                                            |
| `hide`/`show` | Turns visibility on/off for any variable named in the variable map (ignoring values). |
| `automatic`   | Animate In, Update, or Animate Out. See below.                                        |
| `smart`       | Similar to Automatic, See below.                                                      |

**How Automatic Works**: If the specified title is currently not playing, `automatic` will do `animateIn`; if the title is playing but the variable values are changing, `automatic` will do `update`; but if the title is playing and the variable values have not changed, `automatic` will do `animateOut`.

**How Smart Works**: Does `animateIn` or `update` if new variable values are submitted. Does `animateOut` when all submitted variable values are empty or when an empty variable object `{}` is provided.

**Example:**

Full example to update one specific title with new variable values and animate it on screen:

```javascript
ServiceHandler.scheduler.scheduleAction(
  'animateIn',
  '',
  '{1234-5678-9011}',
  {
    Name: 'Wolverines',
    Logo: 'https://example.com/wolverines/logo.png',
    Background: '#ff00ff00',
    Progress: '0.45',
    Time: ' 4:56pm',
  },
  console.log
);
// `{
//     "command": "schedule",
//     "reply": "schedule",
//     "success": true
// }`
```

**Easy Way to Get Title Ids**

Wherever a `title` parameter is used, you can use either the internal title id, or the name of the title as it appears in the project panel. For the most precise control, we recommend using the title id wherever possible. To get title ids, use a function like this when you first connect to the scheduler.

```javascript
let titlesByName = {};
ServiceHandler.scheduler.scheduleCommand('getTitlesControlInfo', {}, {}, (result) => {
  let data = JSON.parse(result);
  if (data.titles) {
    for (let title of data.titles) titlesByName[title.title] = title.id;
  }
});
/* each title will be an object like this (the real object will have much more detail):
{
	"channel": "0",
	"id": "{adf82825-daea-4db4-a919-b1394d257f1e}",
	"inSwitcher": "0",
	"input": "API Tour: JSON Command Tester",
	"inputs": [
		{
			"name": "API Tour: JSON Command Tester",
			"url": "/API Tour/tour/JsonCommands.html"
		}
	],
	"name": "New Title",
	"playStatus": "Off",
	"renderProgress": "1",
	"status": "Done",
	"type": "title",
	"url": "/API Tour/tour/JsonCommands.html",
	"variables": [
		{
			"value": "Name",
			"variable": "Name"
		},
		{
			"value": "Subtitle",
			"variable": "Subtitle"
		}
	]
}
*/
```

Make sure you read [Recommendations for Efficient Title Updates](/captivate-api/javascript-api-reference/using-the-servicehandler/recommendations-for-efficient-title-updates)


# scheduleCommand Method

Send a command message to the scheduler using JavaScript objects.

This is the single most powerful API call, and it exposes a rich suite of functions for extensive control.

```javascript
// callback version (first three arguments are required)
ServiceHandler.scheduler.scheduleCommand(command, parameters, variables, (res) => {
  let data;
  try {
    data = JSON.parse(res);
  } catch (e) {
    data = { result: res };
  }
  console.log(data);
});

// promise version (results will be parsed for you)
const data = await ServiceHandler.scheduler.scheduleCommand(command, parameters, variables);
console.log(data);
```

* `command`: String containing the command; see below for options.
* `parameters`: JavaScript object of keys and values determined by the command.
* `variables`: JavaScript object of variable names and values.
* `callback`: Function to be called with a **JSON-formatted string** of the command results.

Available commands include but are not limited to these:

| command                | what it does                                                                                        |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
| `schedule`             | Internal version of the `scheduleAction` command.                                                   |
| `macro`                | High level title playback control.                                                                  |
| `setTitleInput`        | Assign an input to the title.                                                                       |
| `removeTitleInput`     | Remove an input from a title.                                                                       |
| `setTitlePosition`     | Reposition a title in the list.                                                                     |
| `loadTitle`            | Load a title from file, and optionally give it a new name, position, and id.                        |
| `saveTitle`            | Save the selected title to file.                                                                    |
| `newTitle`             | Create a new title.                                                                                 |
| `readTitle`            | Get detailed information on a title graphic and its variables.                                      |
| `setDynamic`           | Specify whether a variable is allowed to change in a title by setting dynamic to 1 or 0             |
| `downloadImage`        | Download image from a url or parse from a `data:` string, save it to temp file, and return the path |
| `getPlayStatus`        | Get the playback status of the specific title and variables.                                        |
| `getRenderStatus`      | Get the render status of a title.                                                                   |
| `getTitlesPlayStatus`  | Returns a list of all titles and their input and play status.                                       |
| `getInputsPlayStatus`  | Returns a list of all inputs and play status on the requested channel.                              |
| `getAllVideoIns`       | Returns a list of all video devices available for input                                             |
| `getAllAudioIns`       | Returns a list of all audio devices available for input                                             |
| `isTitleConnected`     | Returns true if at least one title in the provided channel is connected to one of a list of inputs. |
| `getTitlesConnected`   | Returns a list of all titles that are connected to the specified input (or all if input is empty).  |
| `getTitleControlInfo`  | Get extensive information for all titles, just one title, or titles connected to the input.         |
| `getTitleConnnectInfo` | Get all information pertaining to the connection between inputs and titles.                         |
| `getCachePath`         | Gets the current location for the specified title’s cache                                           |
| `setCachePath`         | Sets a unique location to build the cache for the requested title                                   |
| `subscribe`            | Subscribe for notifications when titles change play state or their data is updated.                 |
| `unSubscribe`          | Unsubscribe for notifications when titles change play state or their data is updated.               |
| `getSnapShot`          | Request a rendering of a title with specific variable settings - can be used to preview.            |
| `getTitleIcon`         | Request a rendering of a title, but only if it has changed since last time.                         |
| `openFileDialog`       | Open a file browser to select a file and return its full path.                                      |
| `getFileContent`       | Read a file (or url) and return its data - useful for files that need to be polled for changes.     |
| `fetchUrl`             | Perform an arbitrary web request similar to JavaScript's `fetch` command                            |
| `encodeImage`          | Encodes an image from a file to a dataUrl for easy display in a web UI.                             |
| `assignVariable`       | Assign an input variable to a graphic variable.                                                     |
| `popupAssignVariable`  | Open a popup menu to connect the variable to a field in the title.                                  |
| `openDesigner`         | Open the title designer with the requested title.                                                   |
| `closeDesigner`        | Close the title designer.                                                                           |
| `openUrl`              | Open URL in the default system browser.                                                             |
| `... custom message`   | When using the `to` field, your message will be passed to passed to other controllers               |

> Note: Most commands can be targeted to a specific channel, and the channel should be specified as a number starting at `0`. This is different from the way channels are displayed in the project panel. The tab labeled “Channel 1” is accessed as channel `0`.

**Example:**

```javascript
// remember, when using the promises version, the response data will be parsed for you
const res = await ServiceHandler.scheduler.scheduleCommand(
  'downloadImage',
  { url: 'http://my-images.com/image.jpg' },
  {}
);
if (res.success) {
  console.log(`Image downloaded to local cache: ${res.path}`);
  ServiceHandler.scheduler.scheduleAction(action, inputName, titleId, { 'My Image': res.path });
}
```

There are many more commands than these, including commands to start and join Zoom meetings, commands to retrieve chat messages from a video conference. For a full description of these and all currently available commands, view the [All Commands Reference](/captivate-api/all-commands-reference).

Additionally, we provide a [JSON Command Tester](/api-examples/json-command-tester) included in the `API Tour` data controller. You can use that controller to easily test each command and copy/paste code directly from it.

<figure><img src="/files/v8zJypiymrRu5m1pAhyf" alt=""><figcaption></figcaption></figure>


# Understanding Signals

The Captivate backend exposes a variety of “signals” that you can connect to for getting instant updates from the Captivate backend.

## Signals

### connecting

To connect to one of these signals, call the connect method (i.e. `scheduler.[SIGNALNAME].connect`) on the desired signal and provide a callback that will handle the signal data. Signal data will be different for each signal and is specified in the list below.

### disconnecting

Signals expose a `disconnect` method you can use to disconnect your callback from a signal, but our library makes it easier for you by returning a "canceler" function that you can use to disconnect your callback from the signal. Using the canceler function allows you to use an anonymous function for the callback and still be able to disconnect it from the signal later.

```javascript
const canceler = SeriviceHandler.scheduler.onNotify(() => {
  console.log('we got a notification!!');
});
// ... some time later
canceler(); // all cleaned up
```

> **Note:** If you call the connect method with an anonymous function, you must use the returned canceler function. There won't be a way to call disconnect with the same function in the future because it was anonymous.

The most useful of all the signals is `onNotify` since it allows you to receive updates whenever a title’s play status or data variables change. Use this in combination with a `subscribe` command according to the example below.

* `onNotify` - Calls the callback with a JSON-encoded string of notification data.
  * Includes data requested from a previous `subscribe` command.
  * Includes all commands passing through the command bus.
* `newCommandXML` - Calls the callback with an XML document that represents the last command executed in the system.
* `messageIn` - When another controller calls the `messageOut` method, the connected function will be called with three arguments: `from`, `to`, `data` where `data` is the JSON-encoded data from the message.
* `redirected` - Informs when the HTML page in the Data Controller is redirected (rarely used).
* `serverPortChanged` - When server ports were changed (rarely used).
* `settingsChanged` - The connected function will be called with the `inputName` as the first argument whenever any controller settings are saved. If you get this, you should check to see if the `inputName` matches your controller and then maybe request the latest settings using `loadSettings`. *NOTE: Beware of creating infinite loops.*
* `variablesChanged` - The function you connect will be called with the `inputName` as the first argument (or `"all"` if an input name was not specified in the update command) and a map of changed variables as the second argument.
  * This function doesn’t work entirely as expected. It was originally designed to keep controller UI in sync with the true state of the Captivate system, but it only reports *controller* variable changes made through API calls. Remember that editing a variable value from the program UI (Live Data panel, Values Grid, etc) directly affects the *graphic* variable and therefore doesn’t trigger this callback. If you want to keep track of *graphic* variable updates, you should `subscribe` to the `data` event.

### Example:

```javascript
/**
 * Because there is a single server for all HTML inputs, this callback will be fired for
 * *any* variable change and will have data that might not be relevant to this controller,
 * so be sure to filter on the input name.
 */
scheduler.variablesChanged.connect((inputName, variables) => {
  if (inputName == INPUT_NAME) {
    console.log('variables changed', variables);
  }
});
```

## Full Notification Example with Subscription

To make full use of the `onNotify` signal, you need to also understand the `subscribe` command, so this example shows how to do that

### Example:

```javascript
// assuming the scheduler is already connected!!

const my_name = 'My Subscriber';
const my_input = ServiceHandler.inputName;
const scheduler = ServiceHandler.scheduler;

/**
 * The `subscribe` command tells Captivate to register a new "subscription" or modify an existing one.
 *
 * Internally, subscriptions are identified by the value sent as the "sender" and many data controllers
 * leave this value blank to share the existing notification sender in the system.
 *
 * However, if you want to control what your data controller sees, it's helpful to specify your own sender
 * and customize the events, titles, and/or inputs that send notifications.
 *
 * If a subsequent "subscribe" command is sent with the same sender name, any new parameters will be
 * appended to the existing ones.
 *
 * If you register a subscription with a sender, it's good practice to make sure you later send an
 * unsubscribe command with that same sender name and event list so it gets cleaned up inside Captivate
 *
 * The following command will create a new sender identified by `my_name` and will ask for all
 * play and data notifications that relate to the specified input.
 */
scheduler.scheduleCommand('subscribe', { input: my_input, events: 'play,data', sender: my_name }, {});

// setup the callback
const disconnect = scheduler.onNotify.connect((messageText) => {
  // Convert the payload string into a JavaScript object.
  let data = JSON.parse(messageText);

  // The WebSocket API sends all notifications to all controllers,
  // so ignore the ones we don't want.
  if (data.sender != my_name) return;

  // log the data
  console.log(data);
});

// connect the callback
scheduler.onNotify.connect(handler);

// ... some time later... to cleanup
disconnect();
scheduler.scheduleCommand('unsubscribe', { sender: my_name, events: 'play,data' }, {});
```

### A Note About Notification Senders

The sender name you specify in the subscribe command creates a subscription object in the Captivate backend. You can modify it by adding or removing event subscriptions as you wish. This can also be useful in your message handling code to make sure you are only paying attention to messages that come from the sender you are interested in. However, the tight coupling of sender and notification messages only happens with the [External API connection methods.](/captivate-api/advanced-api-reference)

The WebSocket API, for legacy reasons, currently sends all notification messages to all WebSocket clients regardless of what sender they have subscribed to and it does this for every inter-process message whether the target input can be found or not. Since the other API connection methods are more selective with their message passing, the WebSocket API might move in that direction in future releases. Therefore, for your controllers, it’s good practice to specify a sender name for all subscribe operations, and to have your notification handler discard all messages that don’t come from that sender.


# Persisting Controller State

There are a number of cases where you will need to save the state of a controller:

* You want to run the controller in an external browser but want the settings in the browser instance to be the same as the one in the Captivate UI
* You want to have the settings loaded whenever you open the same project
* You want to have the settings loaded whenever you add this controller to any project

The following methods are available for that:

#### `saveSettings` method <a href="#savesettings-method" id="savesettings-method"></a>

Store an input-specific settings string with the project.

```javascript
// callback method
ServiceHandler.scheduler.saveSettings(inputName, settingsString, callback);

// promises method
await ServiceHandler.scheduler.saveSettings(inputName, settingsString);
```

* `inputName`: Name of the input that should hold the settings (can be anything for short-term settings, but must match the name of an attached input for it to be saved with the project).
* `settingsString`: String value to store.
* `callback`: This function will always be called with a single `null` argument

**Example:**

```javascript
let settings = {
  version: 1,
  showColors: true,
  myLabel: 'Hello, World',
};

// note the use of JSON.stringify here
ServiceHandler.scheduler.saveSettings('Simple HTML Input', JSON.stringify(settings));
```

#### `loadSettings` method <a href="#loadsettings-method" id="loadsettings-method"></a>

Load an input-specific settings string that was saved with the project.

```javascript
// callback method
ServiceHandler.scheduler.loadSettings(inputName, callback);

// promises method
await ServiceHandler.scheduler.loadSettings(inputName);
```

* `inputName`: Input behavior name matching the defintion XML.
* `callback`: Function with a single argument that will receive the settings *string* once it is retrieved.

**Example:**

```javascript
ServiceHandler.scheduler.loadSettings('Simple HTML Input', (settingsString) => {
  try {
    let settings = JSON.parse(settingsString);
    // process settings
  } catch (e) {
    // handle JSON parsing errors here.
  }
});
```

#### `saveGlobal` method <a href="#saveglobal-method" id="saveglobal-method"></a>

Save a string of data to the Captivate system so it can be used by any controller in any project.

```javascript
// callback method
ServiceHandler.scheduler.saveGlobal(settingsKey, settingsString, callback);

// promises method
await ServiceHandler.scheduler.saveGlobal(settingsKey, settingsString);
```

* `settingsKey`: The key used to identify this settings string in the global settings database.
* `settingsString`: String value to store.

**Example:**

```javascript
const globalSettingsName = 'My Controller: Globals';
const settings = {
  version: 1,
  showColors: true,
  myLabel: 'Hello, World',
};

// note the use of JSON.stringify here
ServiceHandler.scheduler.saveGlobal(globalSettingsName, JSON.stringify(settings));
```

#### `loadGlobal` method <a href="#loadglobal-method" id="loadglobal-method"></a>

Load a settings string that was saved to the global settings with a specific key.

```javascript
// callback method
ServiceHandler.scheduler.loadGlobal(settingsKey, callback);

// promises method
await ServiceHandler.scheduler.loadGlobal(settingsKey);
```

* `key`: The key used to identify this settings string in the global settings database.

**Example:**

```javascript
const globalSettingsName = 'My Controller: Globals';
const settingsString = await ServiceHandler.scheduler.loadSettings(globalSettingsName);
try {
  let settings = JSON.parse(settingsString);
  // process settings
} catch (e) {
  // handle JSON parsing errors here.
}
```


# Additional Methods with Scheduler

#### `getTime` method <a href="#gettime-method" id="gettime-method"></a>

Asynchronously get the current *timestamp in seconds* (floating point) since Captivate started.

```javascript
ServiceHandler.scheduler.getTime(callback);
```

* `callback`: Function taking a single argument that will receive the time value once it is retrieved.

**Example:**

```javascript
ServiceHandler.scheduler.getTime((time) => {
  // do something with time
  console.log(`Captivate has been running for ${time} seconds`);
});
```

#### `updateInputDefinition` method <a href="#updateinputdefinition-method" id="updateinputdefinition-method"></a>

Updates the definition of the named input with new variables and types. This overrides all variable definitions specified in the XML file and is necessary when a new variable name is added to the input or when a variable pattern needs to be changed. This function will also trigger a re-render on all titles connected to this input, so use it sparingly.

```javascript
ServiceHandler.scheduler.updateInputDefinition(inputName, definition);
```

* `inputName`: Input name must match the name from the input definition XML file.
* `definition`: JavaScript object containing the new variable definitions (see below). By default, this definition will completely replace the variables defined previously. That is, new variables will be created if they weren’t defined before, and variables omitted from this definition will be deleted from the input for this project. However, you may send a `method` parameter set to one of the following values to change this behavior:
  * `reset`: All current variables will be deleted, and the supplied variables will constitute the new definition. (This is the default for backwards compatibility, but if there are a lot of variables, this might cause an expensive re-render. Use the `merge` option whenever you can.)
  * `delete`: Supplied variables will be deleted from the input definition.
  * `merge`: Supplied variables will be added to the input definition, possibly replacing previous variables.

For more information on the variable definition, see **Creating Input Behaviors** section of the [XML Definition](/captivate-api/captivate-controller-reference/xml-definition) page for description of definition parameters. They are the same here as in the XML specification with the exception that both attributes and child nodes from the XML are represented in JavaScript by object fields. Specifically notice that `<value>` nodes are here represented by a `values` list.

**Example:**

```javascript
const definition = {
  method: 'reset', // if omitted, `reset` is the default, may also be `delete` or `merge`
  variables: {
    'Home Score': {
      type: 'text',
    },
    'Visitor Score': {
      type: 'text',
    },
    'Shot Clock': {
      type: 'text',
      pattern: '[0-2][0-9].[0-9]',
    },
    'Period Number': {
      type: 'text',
      values: ['1', '2', '2.5', '3', '4', '5'], // 2.5 = halftime, 5 = OT
    },
  },
};

ServiceHandler.scheduler.updateInputDefinition('Simple HTML Scoreboard', definition);
```


# Communication Between Controllers

Because a controller can operate in the Captivate UI and also in a local browser, it’s quite possible for you to have multiple instances of the same controller in two different places both communicating with Captivate. This can cause problems unless the two instances of the controller know about each other and are able to communicate.

In fact, there are many reasons why one controller would want to communicate directly with another controller, so Captivate provides two methods designed to address this specific situation. For example, if you have one controller that has one UI running in Captivate and another UI designed for a remote operator, you can keep both interfaces in sync by sending messages through Captivate.

## Using `messageIn` and `messageOut`

Controllers can pass messages back and forth to other controllers or to other instances of themselves by using the `messageOut` method in combination with the `messageIn` signal.

To use, call the `messageOut` method with three arguments:

* `from` - A string identifying the source of the message (usually the controller name, but may be anything).
* `to` - A string identifying the target of the message (usually the other controller name, but may be anything).
* `data` - A JavaScript object of key, value pairs. This may be an arbitrarily deeply nested object, but ***it must be an object***. Strings, arrays, and other primitives will result in an error.

> **Note:** The "to" field doesn't actually do any message routing in the backend. When using `messageOut`, ALL subscribers to the `messageIn` event emitter will receive the same three arguments and must do their own filtering. Additionally, this means that anything connected to the Captivate API will be able to read any messages sent this way.

**Example:**

```javascript
const my_id = '12345678';
const target_id = '87654321';
const scheduler = ServiceHandler.scheduler;
scheduler.messageIn.connect((from, to, json) => {
  // filter out unwanted messages
  if (to != my_id) return;
  console.log('messageIn received');
  console.log({
    from,
    to,
    data: JSON.parse(json),
  });
});

scheduler.messageOut(
  my_id,
  target_id,
  {
    hello: 'world',
    list: [1, 2, 3],
    nested: { level: { deep: 0 } },
  },
  (e) => console.log((e === `Sent to receiver`) ? 'success!' : 'failed to send');
);
```

## Using Targeted Commands

Instead of `messageIn` and `messageOut`, you can also use `scheduleCommand` to send targeted commands between controllers.

To turn a command message into a targeted command, add a `to` field to your parameters object. It’s also helpful to use a `sender` field as you'll see below.

> **Note:** The `sender` in a targeted command is not related to the `sender` field in our notifications. It's merely a convention our internal controllers use to identify what input sent the command. You could just as well use `from` for this purpose in your controllers.

### Sending

Ordinarily, this command will return a large amount of data about the current project:

```javascript
await ServiceHandler.scheduler.scheduleCommand('getTitleControlInfo', {}, {});
```

However, by adding a `to` field to the parameters Captivate will *ignore the command internally* and just pass the entire payload around to the other inputs (with a few modifications, see below).

If the targeted input can’t be found, Captivate will reply with an error, but whether the other input is found or not, the command message will always be ignored by Captivate and the payload will always get sent to all other controllers listening on the notification stream.

```javascript
const me = 'My Controller: Instance 2';
const target = 'My Controller: Instance 1';
await ServiceHandler.scheduler.scheduleCommand('my-custom-command', { to: target, sender: me }, {});
```

If the specified input was connected to a title in the project, this is the result:

```json
{
  "command": "my-custom-command",
  "reply": "my-custom-command",
  "success": true
}
```

If the input is not connected to any project title, this is the result:

```json
{
  "command": "my-custom-command",
  "reply": "my-custom-command",
  "error": "No such input",
  "success": false
}
```

Regardless of whether the input is found, the command is ignored by Captivate and the following payload will be sent through the notification system:

```json
{
  "command": "my-custom-command",
  "to": "My Controller: Instance 1",
  "sender": "My Controller: Instance 2"
}
```

### Receiving

To receive targeted messages, you must be listening to the notification stream. Subscribing to notifications is described elsewhere, but here's an example again.

**Example:**

Make sure only one instance of the controller is allowed to send API commands:

```javascript
let weAreControlling = true;
const selfId = (selfId = Math.floor(Math.random() * 1e20)
  .toString(36)
  .padStart(8, 'X')
  .substring(0, 8));
const ourName = 'My Controller';

async function requestControl() {
  weAreControlling = true;
  await ServiceHandler.scheduler.scheduleCommand('request_control', { to: ourName, sender: selfId }, {});
}

ServiceHandler.scheduler.scheduleCommand('subscribe', {}, {});
ServiceHandler.scheduler.onNotify.connect((msg) => {
  /* handle notification messages here */
  const data = JSON.parse(msg);
  // ignore messages that we sent
  if (data.sender === selfId) return;

  if (data.to === ourName) {
    switch (data.command) {
      case 'request_control':
        // the other instance is requesting control
        weAreControlling = false;
      default:
        console.log(data);
    }
  }
});
```

### Targeted Command Payload Restrictions

The payload sent to the notification stream is assembled according to the following rules:

* The first argument passed (command name) will populate the `command` field in the JSON payload.
* All key/value pairs from the second argument, what we call `parameters`, will be included as-is in the notification payload.
* The third argument, what we call `variables`, will be included in the notification payload as a child of the `variables` key.

Because of this, controllers can send and receive messages with arbitrary data in the form of key/value pairs by simply adding more fields to the original message payload or by including these values in the `variables` argument. Here is a more thorough example:

```javascript
ServiceHandler.scheduler.scheduleCommand(
  'greetings',
  {
    to: 'API Tour: JSON Command Tester',
    key1: 'hello',
    key2: 'world',
    key3: {
      hello: 'world',
    },
    key4: 1,
  },
  {
    data1: {
      hello: 'world',
    },
    data2: 'happiness',
  },
  console.log
);
```

This will be printed to the console (assuming the target controller exists in the project):

```json
{
  "command": "greetings",
  "reply": "greetings",
  "success": true
}
```

...and this is the JSON payload that will propagate through the notification system:

```json
{
  "command": "greetings",
  "key1": "hello",
  "key2": "world",
  "key3": {
    "hello": "world"
  },
  "key4": 1,
  "to": "API Tour: JSON Command Tester",
  "variables": {
    "data1": {
      "hello": "world"
    },
    "data2": "happiness"
  }
}
```


# Recommendations for Efficient Updates

When you have a heavily animated graphic that takes multiple seconds to render, consider pre-rendering values in advance. As soon as you know the value of the variable you will want to display, send a `render` action for that specific variable to the specific title and then wait before sending the actual `update` or `animateIn` action.

You can determine the current render status for any title by using the `renderStatus` command described under the `scheduleCommand` section.

Additionally, whenever your graphic is heavily dependent on numerical data, consider setting up those numbers as [pattern variables](/captivate-api/captivate-controller-reference/pattern-variables) in your graphic, in your controller's XML definition, or issuing a `updateInputDefinition` command to add a pattern to an existing variable. When you use a pattern for a text variable, all possible values of each digit are rendered separately in advance, so they can be used immediately when needed. As a side benefit, you also get your animation applied to each digit independently.

**Summary:**

* Set up your graphic to use pattern variables for every numeric value that needs to update efficiently.
* Whenever you know some variable data in advance of when it needs to be displayed and using a pattern doesn't make sense (e.g. names in a baseball line-up, or a team’s jersey numbers), send a `render` action for each possible value of that variable.

By sending `render` as soon as you can, you ensure your title will be completely ready for any data you send to it.


# Legacy Scheduler Methods

In this section, you will find a few methods that are used in existing Captivate controllers, but have since been replaced by better, easier, or more powerful options. These methods still exist for compatibility with those older controllers, and shouldn't be used in newer controllers.

They are documented here for your reference.


# "For Key" Methods

#### `setValueForKey` / `getValueForKey` / `mergeValueForKey` methods <a href="#setvalueforkey--getvalueforkey--mergevalueforkey-methods" id="setvalueforkey--getvalueforkey--mergevalueforkey-methods"></a>

You can store and retrieve arbitrary data in the current Captivate instance using these methods.

The data will persist even if the project is closed and another is opened, but **will be deleted on app quit**.

* `setValueForKey` - Takes three arguments, a string key, a JavaScript map of what should be stored with that key, and a callback to be called with the results of the operation.
* `getValueForKey` - Takes two arguments, a string key and a callback to be called with the value stored with that key.
* `mergeValueForKey` - Takes three arguments, a string key, a JavaScript map that should be merged with the previous contents, and a callback to be called with the results of the operation.


# scheduleCommandXML Method

The earliest versions of the API operated by passing XML documents around, and that method is still available through the `scheduleCommandXml` function. For example, this is how to call `readTitle`.

```javascript
let s = ServiceHandler.scheduler;
s.scheduleCommandXml(
  '<newblue_ext command="readTitle" channel="-1" input="API Tour: JSON Command Tester" />',
  callback
);

// or when using promises ...
await s.scheduleCommandXml('<newblue_ext command="readTitle" channel="-1" input="API Tour: JSON Command Tester" />');

// is essentially equivalent to this:
await s.scheduleCommand('readTitle', { channel: -1, input: 'API Tour: JSON Command Tester' }, {});
```

The xml document should be constructed with a root node named `newblue_ext` with parameters specified as attributes on the node, and variables specified as child nodes if relevant to the command.

Captivate will respond by passing the following XML to the callback function:

```xml
<newblue_ext command="readTitle" reply="readTitle" success="1">
    <title position="4" ready="1" name="Carbon Basketball Scoreboard" input="Shared: Objects,API Tour: JSON Command Tester" id="{05930c36-bd6d-4350-80cd-f58b476fbde9}" function="" inputActive="1" selected="0">
        <running time="111.6" playInStart="0" playInEnd="0" playOutStart="0" playOutEnd="0" playHead="0" playState="Off" />
        <variables>
            <variable name="Clock 1" value="00:00" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Clock 2" value="0" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Home Score" value="0" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Home Short Name" value="HOME" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Home Timeouts" value="" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Period" value="1" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Visitor Score" value="0" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Visitor Short Name" value="VSTR" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Visitor Timeouts" value="" varType="128" dataType="text" dynamic="1" input="" show="1" />
            <variable name="Home Poss." value="1" varType="128" dataType="visible" dynamic="1" input="" show="1" />
            <variable name="Message" value="1" varType="128" dataType="visible" dynamic="1" input="" show="1" />
            <variable name="Visitor Poss." value="1" varType="128" dataType="visible" dynamic="1" input="" show="1" />
            <variable name="Home Logo" value="/Library/Application Support/NewBlue/Titler Content/Resources/Library/Project Templates/Sports/Carbon/textures/Placeholder-A.png\\\0" varType="128" dataType="style-texture.texture" dynamic="1" input="" show="1" />
            <variable name="Visitor Logo" value="/Library/Application Support/NewBlue/Titler Content/Resources/Library/Project Templates/Sports/Carbon/textures/Placeholder-B.png\\\0" varType="128" dataType="style-texture.texture" dynamic="1" input="" show="1" />
            <variable name="Duration" value="1.502" varType="128" dataType="duration" dynamic="1" input="" show="0" />
            <variable name="Home Color" value="#ff1e1e1e" varType="0" dataType="style-texture.color" dynamic="0" input="" />
            <variable name="Visitor Color" value="#ff2065ab" varType="0" dataType="style-texture.color" dynamic="0" input="" />
            <variable name="Position" value="0,0" varType="0" dataType="position" dynamic="0" input="" />
            <variable name="Scale" value="1,1" varType="0" dataType="scale" dynamic="0" input="" />
        </variables>
    </title>
</newblue_ext>
```

Notice that the reported `inputName` has all connected inputs separated by a comma.


# scheduleVariables and scheduleVariablesEx Methods

#### `scheduleVariables` <a href="#schedulevariables" id="schedulevariables"></a>

`scheduleVariables` takes these arguments: `inputName`, `queuingMode`, `queueName`, and `variables` (where queuing mode is one of the following: `update`, `play`). Some older data controllers use this function, but since it doesn’t support sending commands to a specific title, it is less useful than `scheduleAction` and its use is therefore discouraged.

```javascript
let s = ServiceHandler.scheduler;
let inputName = 'Test: Test Actions';
s.scheduleVariables(inputName, 'play', '', { Message: 'Greetings!' });
```

#### `scheduleVariablesEx` method <a href="#schedulevariablesex-method" id="schedulevariablesex-method"></a>

This is an advanced command that can be used in place of `scheduleAction` when the action you want is complicated and/or the provided presets are not enough. See Actions List for all possible actions.

```javascript
// Called from JavaScript. Update and schedule variables with advanced controls.
ServiceHandler.scheduler?scheduleVariablesEx(action, time, inputName, channelName, queueName, titleId, startSegmentName, endSegmentName, variables);
```

* `action`: Semicolon-separated list of actions to perform in sequence.
* `time`: Ccheduler time (retrieve with `getTime()`) when animations should play (variables are updated internally immediately).
* `inputName`: Limit this action to the named input.
* `channelName`: Limit this action to the named channel.
* `queueName`: Limit this action to the named queue.
* `titleId`: Limit this action to the named title (by id).
* `startSegmentName`: Specify the starting segment by name if relevant.
* `endSegmentName`: Specify the ending segment by name if relevant.
* `variables`: JavaScript map of variable, value pairs; names must match the current definition.

**Example:**

```javascript
// This command expects time as the number of seconds since the Captivate program was launched.
// It can be left blank or set to 0 to run the action immediately.
// Remember the variables update immediately. Only the animations honor the delay.
// Therefore, the following function will do the following:
// - immediately update the variables in the preview window
// - wait 2.5 seconds before animating the variables to the program
// - wait another 2.5 seconds and then animate out
let s = ServiceHandler.scheduler;
let inputName = 'Test: Test Actions';
s.getTime((t) => {
  s.scheduleVariablesEx('animateIn', t + 2.5, inputName, '', '', '', '', '', { Message: 'Greetings!' });
  s.scheduleVariablesEx('animateOut', t + 5, inputName, '', '', '', '', '', {});
});
```


# External API Reference

The following pages will show you alternative ways of communicating with our API such as HTTP, TCP, and UDP. These methods are useful primarily when you are not calling the API from a data controller. If you are building a JavaScript-based data controller, we recommend you stick to the [Controller API](/captivate-api/javascript-api-reference).


# TCP, UDP, and HTTP Connections

Our primary API operates over a websocket connection with various JavaScript wrapper libraries. However, websocket communication is not universally supported and can be cumbersome to implement from outside the context of a browser. Therefore, we provide three additional communication methods:

* TCP socket (persistent communication)
* UDP socket (one-shot communication)
* HTTP GET/POST endpoints (two-way communication using chunked responses)

Each of these methods gives you new ways to access the `ServiceHandler.scheduler` object, and each of these endpoints handles data communication in roughly the same way (although the method of connecting to them is different).

Before reading further, make sure you are familiar with the [Controller API](/captivate-api/javascript-api-reference) because this API depends upon that.

### MDNS Discovery (Bonjour) <a href="#mdns-discovery-bonjour" id="mdns-discovery-bonjour"></a>

We broadcast our API using MDNS (bonjour) technology with the following PTR names:

* `_newblue._tcp.local`
* `_newblue._udp.local`

In the answers, you will see the following service names (IDENTIFIER will be constructed from the computer’s IP address):

* `Captivate-WS-API-[IDENTIFIER]._newblue._tcp.local`: WebSocket API service
* `Captivate-HTTP-API-[IDENTIFIER]._newblue._tcp.local`: Advanced API over HTTP
* `Captivate-TCP-API-[IDENTIFIER]._newblue._tcp.local`: Advanced API over TCP
* `Captivate-UDP-API-[IDENTIFIER]._newblue._udp.local`: Advanced API over UDP
* `Captivate-UDP-FinishLynx-[IDENTIFIER]._newblue._udp.local`: Experimental FinishLynx support

You may also query for the following deprecated PTR name:

* `_captivate-tcp-api._tcp.local`

A `PTR` query on one of our service names (names must be exact, do not use a trailing dot) will return an answer for each of our services with the following structure:

```javascript
{
  answers: [
    {
      name: '_newblue._tcp.local',
      type: 'PTR',
      ttl: 120,
      class: 'IN',
      flush: false,
      data: 'Captivate-WS-API-192-168-10-50._newblue._tcp.local'
    }
  ],
  additionals: [
    {
      name: 'Captivate-WS-API-192-168-10-50._newblue._tcp.local',
      type: 'SRV',
      ttl: 120,
      class: 'IN',
      flush: true,
      data: {
        weight: 0,
        priority: 10,
        port: 9023,
        target: 'Captivate-WS-API-192-168-10-50._newblue._tcp.local'
      }
    },
    {
      name: 'Captivate-WS-API-192-168-10-50._newblue._tcp.local',
      type: 'A',
      ttl: 120,
      class: 'IN',
      flush: true,
      data: '192.168.10.50'
    },
    {
      name: 'Captivate-WS-API-192-168-10-50._newblue._tcp.local',
      type: 'TXT',
      ttl: 4500,
      class: 'IN',
      flush: true,
      data: [
        'hlp=default qwebchannel-based api',
        'ip=192.168.10.50',
        'id=3z4jv021t',
        'port=9023'
      ]
    }
  ]
}
```

* The `PTR` record will contain the name of the `SRV` record which will include a unique identifier for that service.
* The `SRV` record will expose the port for that service.
* The `A` record that will give the IP address.

### Simple Variable Update method <a href="#simple-variable-update-method" id="simple-variable-update-method"></a>

Because the majority of external APIs really only need to update variables, we have implemented a simplified endpoint for doing just that.

Send a POST request with `Content-type: application/json` to `/api/action/ACTION/TARGET/TARGET_NAME` with the following:

* `ACTION` needs to be one of the [`scheduleAction`](/api-examples/sample-controllers-tour/01-schedule-action) actions such as `render`, `update`, `still`, `animateIn`, `animateOut`, etc.
* `TARGET` can be either `input` or `title`.
* When targeting an input, `TARGET_NAME` should be the full name of the data controller connected to the title layer.
* When targeting a title, `TARGET_NAME` may be either the textual name of the title as it shows up in the project list, or the internal title id for the title.
* The request body should be a simple mapping of variable names to values encoded as JSON.

**Example:**

```sh
curl -H 'Content-type: application/json' \
     --data '{"Home Score": 13}' \
     'http://localhost:9022/api/action/update/input/Scoreboard%20OCR%3A%20Basketball'
```

### Using the Advanced API over HTTP <a href="#using-the-advanced-api-over-http" id="using-the-advanced-api-over-http"></a>

The HTTP endpoint is exposed at port `9022` at path `/api`. This port must not be in use when Captivate launches or the HTTP endpoint will not be able to start.

The HTTP endpoint responds to GET requests at `/api` with a brief help text similar to the TCP welcome message.

To send an API command, send a POST request to `/api` with `Content-type: application/json` and a JSON-encoded string as the request body.

To subscribe to Captivate’s notification stream, send a GET request to `/api/subscribe`. The server will keep the connection open and reply with a chunked HTTP response. Each notification from Captivate will be served as a JSON-encoded string chunk.

> Note: We also support a number of extra methods over HTTP. You can see documentation for them [here](/captivate-api/advanced-api-reference/additional-http-endpoints).

### Using the Advanced API over TCP <a href="#using-the-advanced-api-over-tcp" id="using-the-advanced-api-over-tcp"></a>

The TCP endpoint is exposed at port `9025`. This port must not be in use when Captivate launches or the TCP endpoint will not be able to start.

When a client connects, a welcome message will be sent from Captivate. The welcome message will be something like this:

```
CAPTIVATE HELLO
=======================================
We support a subset of Titler's API over direct TCP socket communication using JSON.

All messages are terminated with CRLF. '\r\n'


To call "scheduleCommand", send a JSON-encoded string that will decode with the following fields:

  - command: The name of the command to run
  - parameters: The parameters as an object
  - variables: The variables as an object

  The results of the command will be output as a JSON-encoded string.

To call "scheduleAction", send a JSON-encoded string that will decode with the following fields:

  - action: The name of the action to schedule
  - input: The name of the input / data controller to target
  - title: The id of the title / graphic layer to target
  - variables: The variables as an object

  The results of the command will be output as a JSON-encoded string.

Additionally, as long as you are connected, we will send you Titler's notification stream.
-------------------------------------------------------------------------------------------
BEGIN DATA
```

The welcome message ends with:

* The text `BEGIN DATA`
* *Two* sets of CRLF. (`\r\n\r\n`)

After the welcome message, all messages sent to or from this socket will be JSON-encoded strings, terminated with *one* set of CRLF. (`\r\n`)

As long as this client is connected to the TCP endpoint, all notification messages and signals will be sent to the client.

> Note: If a client connects and immediately sends a message, the server will *not* send the welcome message and will also *not* subscribe the client to notification messages and signals. If you connected with an immediate message and want to subscribe to notification messages send the command `subscribe` with empty parameters and variables.

**Example for Mac:**

```sh
MSG=$'{"command": "subscribe"}\r\n'
( echo $MSG; sleep 10; ) | nc localhost 9025
```

### Using the Advanced API over UDP <a href="#using-the-advanced-api-over-udp" id="using-the-advanced-api-over-udp"></a>

The UDP endpoint is exposed at port `9026`. This port must not be in use when Captivate launches or the UDP endpoint will not be able to start.

The UDP endpoint expects to receive a JSON-encoded string and will immediately attempt to process the API call.

There is no notion of a “response” in UDP communication, but the server will attempt to send API responses to the same client and port used by the sender.


# API Communication

### Equivalent to `ServiceHandler.scheduler.scheduleCommand` <a href="#equivalent-to-servicehandlerschedulerschedulecommand" id="equivalent-to-servicehandlerschedulerschedulecommand"></a>

To call `scheduleCommand`, send a JSON-encoded string that will decode to an object with the following fields:

* `command`: *string*. The name of the command to run.
* `parameters`: *object*. The parameters as an object.
* `variables`: *object*. The variables as an object.

**Netcat Example:**

```sh
MSG=$'{"command": "readTitle", "parameters":{"title":"Test"}}\r\n'
( echo $MSG; sleep 1; ) | nc localhost 9025
```

**Curl Example:**

```sh
curl --data '{"command": "readTitle", "parameters":{"title":"Test"}}' \
     -H 'Content-Type: application/json' \
     http://localhost:9022/api/
```

### Equivalent to `ServiceHandler.scheduler.scheduleAction` <a href="#equivalent-to-servicehandlerschedulerscheduleaction" id="equivalent-to-servicehandlerschedulerscheduleaction"></a>

To call `scheduleAction`, send a JSON-encoded string that will decode to an object with the following fields:

* `action`: *string*. The name of the action to schedule.
* `input`: *string*. The name of the input/data controller to target, leave blank to target all.
* `title`: *string*. The id or name of the title/graphic layer to target, leave blank to target all.
* `variables`: *object*. The variables as an object.

```sh
curl --data '{"action": "update", "title": "My Title", "variables": {"Time": "00:00", "Message": "Go!"}}' \
     -H 'Content-Type: application/json' \
     http://localhost:9022/api/
```

### Equivalent to `ServiceHandler.scheduler.updateInputDefinition` <a href="#equivalent-to-servicehandlerschedulerupdateinputdefinition" id="equivalent-to-servicehandlerschedulerupdateinputdefinition"></a>

To call `updateInputDefinition`, send a JSON-encoded string that will decode to an object with the following fields:

* `redefine`: *string*. The name of the input/data controller to target.
* `definition`: *object*. The variables “definition” object (see `updateInputDefinition` on the JavaScript API documentation).

```sh
curl --data '{"redefine": "My Input", {"method": "merge", "variables": {"Clock":{"pattern":"[0-9][0-9]:[0-9][0-9]"}}}}' \
     -H 'Content-Type: application/json' \
     http://localhost:9022/api/
```

### Equivalent to `ServiceHandler.scheduler.saveSettings/loadSettings` <a href="#equivalent-to-servicehandlerschedulersavesettingsloadsettings" id="equivalent-to-servicehandlerschedulersavesettingsloadsettings"></a>

To call `saveSettings` or `loadSettings`, send a JSON-encoded string that will decode to an object with the following fields:

* `settings`: *object*|*string*. For loading, use the word `load`. For saving, use an object or any string other than `load`.
* `input`: *string*. The name of the input / data controller for which to load or save settings.

```sh
# save {"name": "value"} to the settings
curl --data '{"input":"API Tour: JSON Command Tester", "settings": {"name":"value"}}' \
     -H 'Content-Type: application/json' \
     http://localhost:9022/api/
# {"result":null}

# load from the settings
curl --data '{"input":"API Tour: JSON Command Tester", "settings": "load"}' \
     -H 'Content-Type: application/json' \
     http://localhost:9022/api/
# {"result":{"name":"value"}}
```

### Handling Results <a href="#handling-results" id="handling-results"></a>

The results of an advanced API call will be exactly the same as the main JavaScript API with the following modifications:

* Successful responses will be wrapped in an object where `result` is the key to the actual Captivate response.

  ```json
  {
    "result": {
      "command": "schedule",
      "reply": "schedule",
      "success": true
    }
  }
  ```
* If Captivate’s normal response included a `file` (referring to a filepath on the Captivate computer’s filesystem), the wrapped response will also include a `url` field pointing to the same file, but made available over HTTP.
* If the call failed somehow, the response will be `{"error": "scheduleCommand failed", "data": [javascript error object] }`
* If the call is one of the asynchronous commands that passes information back through the notification system, you will need to be subscribed to notifications to receive it. See below.

### Handling Notifications <a href="#handling-notifications" id="handling-notifications"></a>

When a client connects to the TCP socket or issues an HTTP GET to `/api/subscribe`, it will immediately be subscribed to all `play` and `data` events sent from Captivate. Whenever a notification message is received from Captivate, it is passed to the client as a JSON-encoded string terminated with CRLF (`\r`). As with command results, this reply will have additional `url` fields whenever a `file` field is found in the response from Captivate.

The UDP endpoint does not support Captivate’s notification stream.

Notification messages will be wrapped in an object where `notification` is the key to the actual notification content (or `error` as above if there was an error subscribing to the notification stream):

```json
{
  "notification": {
    "channel": -1,
    "command": "notify",
    "event": "play",
    "id": "{cd654eba-4afe-4838-ad75-65ff823ac0dd}",
    "input": "",
    "play": "Running",
    "sender": "", // `sender` as given in the parameters of the "subscribe" command
    "title": "Acrylic Baseball Scoreboard",
    "to": ""
  }
}
```


# Additional HTTP Endpoints

## Overview <a href="#overview" id="overview"></a>

In addition to the API functions described in Controller API Reference and External API Reference, we also have a number of simple HTTP endpoints that will allow you to get certain forms of data quickly and easily.

### Finding/Changing the HTTP Port <a href="#finding-the-base-url" id="finding-the-base-url"></a>

Every time Captivate launches, it attempts to start a web server on port `9022`. If that port is in use already, Captivate will fail to start properly. If something on your system needs to use that port, you can choose a different port by setting the value for `Generic HTML.httpPort` in the Windows Registry or in the Mac user preferences.

Mac example:

```bash
defaults write com.NewBlue.Captivate `Generic HTML.httpPort` 9099
defaults read com.NewBlue.Captivate `Generic HTML.httpPort`
```

> Note: the WebSocket port is usually `9023`, but can be specified in the same way with the registry setting `Generic HTML.webSocketPort`.

### /temp/`FOLDER`/`FILE.EXT` <a href="#tempfolderfileext" id="tempfolderfileext"></a>

Captivate stores all files including images downloaded from the Internet using the `downloadImage` command in a temporary folder assigned by the operating system. The Captivate API can access these local files directly through the file system, but controllers can get them through regular HTTP calls to this endpoint.

**Example:**

```javascript
const url = 'https://newbluefx.com/wp-content/uploads/2019/07/nb_logo.png';
const data = await ServiceHandler.scheduler.scheduleCommand('downloadImage', { url }, {});
/*
{
	"command": "downloadImage",
	"mime": "image/png",
	"path": "/private/var/folders/gj/8bp694f15qb2zhl4q3l5ns700000gn/T/TitlerLive/f94ffb6f-5c98-4f46-a711-36e35f53a1c2/43ab9e0e-7c69-49cf-8530-e0f54dc9bd0e.png",
	"reply": "downloadImage",
	"size": "2601",
	"success": true,
	"tempUrl": "http://192.168.10.50:9022/temp/f94ffb6f-5c98-4f46-a711-36e35f53a1c2/43ab9e0e-7c69-49cf-8530-e0f54dc9bd0e.png",
	"url": "https://newbluefx.com/wp-content/uploads/2019/07/nb_logo.png"
}
*/
console.log(data);
```

### /api/title/`TITLE_ID` <a href="#apititletitle_id" id="apititletitle_id"></a>

Captivate allows you to get the XML definition file for any title using this HTTP call. Just remember to include the curly braces as part of the `TITLE_ID` and urlencode them.

```sh
# Get XML for title {b960a28b-e583-4f3f-b54b-851ef481314d}
curl 'http://localhost:9022/api/title/%7Bb960a28b-e583-4f3f-b54b-851ef481314d%7D'
```

### /api/tunnel

Captivate allows you to get the information on the current websocket tunnel by making a GET request to this endpoint.

```sh
curl 'http://localhost:9022/api/tunnel'
```

## Additional NodeJS-basedased Endpoints <a href="#additional-http-endpoints" id="additional-http-endpoints"></a>

When Captivate runs, it launches a small nodejs-based server behind the scenes. That server also provides a few API endpoints you can access. Many of these endpoints are used by our own controllers, but they are documented here for your use too.

### Using the `/discover/:name` endpoint <a href="#using-the-discovername-endpoint" id="using-the-discovername-endpoint"></a>

Since Captivate uses MDNS to broadcast itself on the network and to find services it knows about, it maintains a database of discovered services and allows you to browse them easily using the API.

```sh
curl 'http://localhost:9024/discover/_googlecast._tcp.local'
```


# Leveraging Captivate to Connect to UDP Servers

Finally, many sources of data are published by TCP/UDP servers, but our data controllers usually run in the context of a web browser which can't do direct network communication. Therefore, Captivate provides an easy way to send and receive messages with TCP/UDP servers.

From a browser context, you will create a WebSocket connection to the Captivate WebSocket server using a special url.

Captivate will then make the network connection for you and proxy all messages back and forth using the following formats:

* By default, packets from the network server will be passed through exactly as they come in, packaged as a WebSocket binary message (`event.data` will be a Blob object).
* By passing the `terminator` query parameter, the packets will be accumulated, split at the terminator, and delivered as individual binary messages.
  * Be sure to percent-encode your terminator string (i.e. `\r\n` will be `terminator=%0d%0a`).
* By passing the `encoding` query parameter, the packets will be decoded according to the specified encoding, converted to `utf-8` and sent as a WebSocket text message. Not all encodings are supported, but these common JavaScript encodings are:
  * `utf-8`, `utf-16`, `utf-16le`, `utf-16be`, `latin1`

## Connecting to a TCP server

Connect a websocket to `/tcp/:address/:port`.

Example using JavaScript.

```javascript
// assuming Captivate's WebSocket server is running on 10.0.0.1, port 9023,
// and assuming your TCP server is running on 10.0.0.2, port 1234,
// connect to it this way

// get all messages as ArrayBuffer data
let ws = new WebSocket('ws://10.0.0.1:9023/tcp/10.0.0.2/1234');
ws.onmessage = async (m) => {
  const bytes = await m.data.arrayBuffer();
  console.log(bytes);
};

// process tcp data as latin1 encoded text and split on every \r\n
let ws2 = new WebSocket('ws://10.0.0.1:9023/tcp/10.0.0.2/1234?encoding=latin1&terminator=%0d%0a');
ws2.onmessage = (m) => console.log(m.data);
```

## Connecting to a UDP server

Connect a websocket to `/udp/:address/:port`.

If you use a multicast address, the connection will automatically attempt to join the multicast group.

Example using JavaScript.

```javascript
// all of these assume the server will send text encoded as utf-8

// send UDP packets to 10.0.0.2 port 1234
let ws = new WebSocket('ws://10.0.0.1:9023/udp/10.0.0.2/1234?encoding=utf-8');
ws.onmessage = (m) => console.log(m.data);

// join multicast group 239.0.0.1 and send/receive UDP data on port 1234
let ws_mc = new WebSocket('ws://10.0.0.1:9023/udp/239.0.0.1/1234?encoding=utf-8');
ws_mc.onmessage = (m) => console.log(m.data);

// listen to any data from any source over port 1234, use `listen` as the address
// e.g. if the server is broadcasting (255.255.255.255 or x.x.x.255)
let ws_bc = new WebSocket('ws://10.0.0.1:9023/udp/listen/1234?encoding=utf-8');
ws_bc.onmessage = (m) => console.log(m.data);
```


# Making Use of our Tunneling Server

### WebSocket Tunnel <a href="#websocket-tunnel" id="websocket-tunnel"></a>

When Captivate launches, it will register a tunnel with our tunnel server running at `https://controllers.newbluefx.com/tunnel`.

The tunnel id will be passed to all data controllers as the query parameter `wsTunnel` and is available through the `/tunnel` endpoint described above.

A controller can then use that tunnel as its connection to Captivate.

**Example:**

```javascript
// Start by reading parameters from the url that launched this.
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const tunnel = urlParams.get('wsTunnel');
ServiceHandler.init(`wss://controllers.newbluefx.com/tunnel/${tunnel}`);
```

> **Note:** The `ServiceHandler.init` function will actually do this automatically if the `wsTunnel` value is found in the query string. In fact, if called with no arguments at all, the `init` function will try all possible connections to find the best way to communicate the Captivate API server.

### WebHook Tunnel <a href="#webhook-tunnel" id="webhook-tunnel"></a>

Finally, some data controllers might benefit from being able to receive data pushed from an arbitrary internet location. For that purpose, you may use our secure cloud-based WebHook tunnel service.

**Example:**

```javascript
let ws = new WebSocket('wss://controllers.newbluefx.com/webhook/new');
ws.onmessage = (m) => console.log(m.data);
ws.onerror = (e) => {
  console.log(e);
};
ws.onclose = () => console.log('closed');
```

The first message received by the websocket will look like this:

```javascript
{
  "event": "status",
  "id": "d9405c37-d1de-11ee-86f0-f61aeaf915ab",
  "url": "/webhook/d9405c37-d1de-11ee-86f0-f61aeaf915ab",
  "status": "connected"
}
```

The server now has a webhook registered at `https://controllers.newbluefx.com/webhook/d9405c37-d1de-11ee-86f0-f61aeaf915ab` and all web requests sent to that endpoint will be forwarded as a message to the websocket.

**Example:**

Sending a message to the webhook tunnel like this:

```sh
curl -H 'content-type: application/json' --data '{"abc": 123}' https://controllers.newbluefx.com/webhook/d9405c37-d1de-11ee-86f0-f61aeaf915ab?test=1
```

will result in the following JSON-encoded message being sent to the websocket:

```json
{
  "event": "webhook",
  "webhook_id": "d9405c37-d1de-11ee-86f0-f61aeaf915ab",
  "message_id": 2,
  "method": "POST",
  "headers": {
    "Accept": ["*/*"],
    "Accept-Encoding": ["gzip"],
    "Cdn-Loop": ["cloudflare"],
    "Cf-Connecting-Ip": ["68.51.12.253"],
    "Cf-Ipcountry": ["US"],
    "Cf-Ray": ["859b39f89d50e13b-ORD"],
    "Cf-Visitor": ["{\"scheme\":\"https\"}"],
    "Connection": ["upgrade"],
    "Content-Length": ["12"],
    "Content-Type": ["application/json"],
    "User-Agent": ["curl/7.79.1"],
    "X-Forwarded-For": ["68.51.12.253"],
    "X-Forwarded-Proto": ["https"]
  },
  "uri": "/webhook/d9405c37-d1de-11ee-86f0-f61aeaf915ab?test=1",
  "body": "{\"abc\": 123}"
}
```


# All Commands Reference

The following pages will show you (almost) all of the commands that Captivate makes available to API consumers.

You can call these commands with the ServiceHandler library by using the `scheduleCommand` method on the `scheduler` object as described in the [JavaScript API Reference](/captivate-api/javascript-api-reference/using-the-servicehandler/schedulecommand-method), and this reference assumes you are familiar with information contained there.

You can also call these commands through the use of one of our [Advanced API](/captivate-api/advanced-api-reference) connection methods.

These reference will describe the commands according to the HTTP connection method but they can easily be translated back to the `scheduleCommand` method.

**Translating from this documentation back to `scheduleCommand`**

For `/api/command/` endpoints:

```javascript
await ServiceHandler.scheduler.scheduleCommand([command], [params], [variables]);
```

* `command` is the path element after `/api/command/`
* `params` is an object described by the `properties` field
* `variables` is an object with the variables to set.
* all arguments are required

For `/api/action/` endpoints:

```javascript
await ServiceHandler.scheduler.scheduleAction([action], [input], [title], [variables]);
```

* `action` is the path element after `/api/action/`
* `input` is the input name to target
* `title` is the id or the name of the title to target
* `variables` is an object with the variables to set
* all arguments are required, so to target just an input or a title, pass an empty string for the other one


# Advanced Control

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api" method="post" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/schedule" method="post" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/customMessage" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/macro" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Audio Commands

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getAllAudioIns" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getOpenedAudioIns" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Cache Commands

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getCachePath" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/rebuildCache" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/saveTitleCache" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setCachePath" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setMaxCacheCount" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# File Tools

Most of the file commands in Captivate can also take a url parameter to download the file from an online resource.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/runDbQuery" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/convertToJson" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/fetchUrl" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/downloadImage" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/encodeImage" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getDirectoryContent" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getFileContent" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/openDirectoryDialog" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/openFileDialog" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/openUrl" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setFileContent" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

> Note: The `fetchUrl` command exists to let you perform web requests when your controller is running in a browser context. Browsers have different security models that can sometimes limit your ability to get data from a remote resource.


# Input Commands

Data controllers are also referred to as inputs and/or profiles. called an input. These commands allow you to creating, modify, and attach profiles and data controllers.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getProfileList" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setTitleInput" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/removeTitleInput" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/attachProfile" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/changeInputName" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setProfile" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

> **NOTE:** `setProfile` requires submitting xml data, so to use it, unless your xml data is small, you might need to send it as a POST request to the `/api/` endpoint directly.


# Instant Replay Commands

Captivate includes instant replay functionality, allowing you to grab previously recorded content and play it back as if it were live. All commands here allow you to grab, set, and playout instant replays.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_arm\_inputs" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_commands" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_disarm\_inputs" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_enable" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_enable\_scrubbing" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_enter\_playback" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_exit\_playback" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_get\_inputs" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_get\_play\_info" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_get\_replay\_speed" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_get\_settings" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_is\_trial\_mode" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_play\_pause" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_set\_in\_point" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_set\_out\_point" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_set\_replay\_speed" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_set\_scrub\_position" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/replay\_set\_settings" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Meeting Control

All of the commands found in this section are related to the Captivate integrations with Microsoft Teams and Zoom. Here, you can join meetings, get the status of a current meeting, or leave a meeting.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/joinMeeting" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/leaveMeeting" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/meetingStatus" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Notifications

Notifications provide alerts based off of specific events. Examples include when titles change play state, when titles data is updated, or when a system event happens.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getSubscriptions" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/subscribe" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/unSubscribe" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Output Commands

All of the commands found here are related to the output of your project, allowing you to configure specific streaming destinations or specific types of output.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getOutputDevice" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getStreamHostServer" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setOutputDevice" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setStreamHostServers" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/startStreaming" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/stopStreaming" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Project Commands

The project commands allow you to manipulate your current project by adjusting specific parameters (i.e. frame rate and resolution) as well as opening, saving, and loading specific projects.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/checkProjectEdited" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/clearPlaylist" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/closeProject" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getCurrentProject" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getRecentProjects" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getVideoFormat" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/loadPlaylist" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/loadProject" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/savePlaylist" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/saveProject" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setVideoFormat" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Status Information

Commands in this section will return information about your current project.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getAppGlobalSettings" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getInputsPlayStatus" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getMonitorFrame" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getPlayStatus" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getPreviewFrame" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getProductInfo" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getProfile" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getRenderStatus" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getReport" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getSnapShot" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getStats" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTime" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTitleControlInfo" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTitleIcon" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTitlesConnected" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTitlesPlayStatus" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getVideoFrame" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/isTitleConnected" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/listCommands" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/readPlaylist" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/readProject" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/readTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Title Commands

All commands found in this section are related to titles. Titles are individual graphic designs that display text, images, and other visual information.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/closeDesigner" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/deleteTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/duplicateTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getTimelineDuration" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/loadTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/newTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/openDesigner" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/saveTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/selectTitle" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setTitleInput" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/removeTitleInput" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setTitleName" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setTitlePosition" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Variable Commands

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/assignVariable" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/setDynamic" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Video Commands

If you are ingesting any live source (SDI, HDMI, NDI, USB, etc.) into Captivate, these video commands will return lists and allow you to manipulate any live source in the switcher.

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/closeVideoIn" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getAllVideoIns" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getOpenedVideoIns" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getVideoInIcon" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/getVideoInInfo" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/openVideoIn" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/QtCUCNFgpSmrzNpGPNiZ" path="/api/command/switchVideoIn" method="get" %}
[api-spec.yaml](https://2284285836-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXkMvk0MrY3BcyplZ0pf7%2Fuploads%2Fgit-blob-145f693d93b2e9832afde71a642305186b556f75%2Fapi-spec.yaml?alt=media)
{% endopenapi %}


# Creating a Simple Controller

The fastest and easiest way to get acquainted with writing Controllers is to walk through the steps of creating a simple one.

### Example Project <a href="#example-project" id="example-project"></a>

Unzip this template project exampleTemplate into a project development folder of your choosing.

{% file src="/files/leVYGrqRooKxYvKeIcKT" %}

Once unzipped, explore the project folders…

At the top level is the “Example App” folder and a batch file for installing the app (we will use this later.)

<div align="center"><figure><img src="/files/XxS0DW1qtpeltI2wyOJR" alt=""><figcaption></figcaption></figure></div>

Click on the Example App folder.

<div align="center"><figure><img src="/files/IeNdJ2qQwDlaZgUUeH8z" alt=""><figcaption></figcaption></figure></div>

`common` includes two JavaScript files necessary for communicating with the API. These must be installed with the app.

`exampleSRC` carries the source for this example.

`example.xml` defines this controller. Captivate reads this to set things up, so you will need to create one of these for each Controller you write.

### Example.xml <a href="#examplexml" id="examplexml"></a>

Let’s start by looking in example.xml:

```xml
<inputBehavior
  name="Example App: Date and Time"
  url="exampleSrc/exampleApp.html"
  iconOn="exampleSrc/alien.png"
  UIGroups="General">
  <variable name="Date" type="text" />
  <variable name="Time" type="text" />
</inputBehavior>
```

`name`: This sets the name of the controller. Note that the part before the colon, “Example App,” must match the name of the parent folder. The part after the colon will be used in the Captivate UI menu, in this case, “Date and Time.” The whole string that identifies this input, must be unique among all controller inputs and should be used in subsequent API calls.

`url`: This provides the location of the actual web page to load. If this were running off a cloud server, it would provide the full URL. In this case, it provides a relative path.

`iconOn`: This is an optional icon file used to display the icon for this controller in the Captivate title/layer list.

Next, this file lists all the variables that this input will provide to any title it is controlling. In this case, there are two variables, “Date” and “Time.” Both are “text” variables, meaning they provide text values.

### ExampleApp.html <a href="#exampleapphtml" id="exampleapphtml"></a>

Now, go into the `examplesSrc` folder where you will find the `aliens.png` and the `exampleApp.html` file.

Open `exampleApp.html` in your code editor:

```html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="../common/js/qwebchannel.js"></script>
    <script type="text/javascript" src="../common/js/servicehandler.js"></script>
    <script type="text/javascript">
      window.onload = function () {
        // Initiate the server connection.
        ServiceHandler.init();
        // Retrieve the name of this input.
        var inputName = ServiceHandler.inputName;

        // We should handle error conditions, such as network loss or something else unexpected.
        ServiceHandler.onclose = function () {
          console.warn('ServiceHandler disconnected');
        };

        ServiceHandler.onerror = function (error) {
          console.error('ServiceHandler error', error);
        };

        // This is our callback when the server is connected and we're ready to access the APIs.
        ServiceHandler.onready = function () {
          // Set up responses to the three button clicks.

          // When the user clicks on the Play In button, set the time and date variables and fly the title in.
          document.getElementById('schedulePlayIn').onclick = function () {
            // Create a json object for variables to set.
            var variables = {};
            // Set the current time and date.
            var timeNow = new Date();
            variables['Time'] = timeNow.toLocaleTimeString();
            variables['Date'] = timeNow.toLocaleDateString();
            // Send the AnimateIn action with this input as the sender, and the new variable settings.
            ServiceHandler.scheduler.scheduleAction('animateIn', ServiceHandler.inputName, '', variables);
          };

          // When the user clicks on the Update button, update with the current time and date.
          document.getElementById('scheduleUpdate').onclick = function () {
            // Create a json object for variables to set.
            var variables = {};
            // Set the current time and date.
            var timeNow = new Date();
            variables['Time'] = timeNow.toLocaleTimeString();
            variables['Date'] = timeNow.toLocaleDateString();
            // Send the update action with this input as the sender, and the new variable settings.
            ServiceHandler.scheduler.scheduleAction('update', ServiceHandler.inputName, '', variables);
          };

          // When the user clicks on the Play Out button, fly the title out.
          document.getElementById('schedulePlayOut').onclick = function () {
            // Send the AnimateIn action with this input as the sender, and no variables.
            ServiceHandler.scheduler.scheduleAction('animateOut', ServiceHandler.inputName, '', {});
          };

          console.info('ServiceHandler connected, ready to send/receive messages!');
        };
      };

      window.onbeforeunload = function () {
        ServiceHandler.scheduler.pageClosed(inputName);
      };
    </script>
  </head>
  <body>
    <div class="w3-container w3-small ">
      <h4>Date and Time</h4>
      <p>Click on buttons to trigger actions:</p>
      <input type="submit" id="schedulePlayIn" value="Play In" />
      <input type="submit" id="scheduleUpdate" value="Update" />
      <input type="submit" id="schedulePlayOut" value="Play Out" />
    </div>
  </body>
</html>
```

As you can see, this is a minimal application.

<figure><img src="/files/dgUcUYLwTVcbeoRMTxNz" alt=""><figcaption></figcaption></figure>

It has three buttons which will play a title in, update its values, and play it out.

Let’s see what each of these does.

Start by looking at the code for the first button:

```javascript
// When the user clicks on the Play In button, set the time and date variables and fly the title in.
document.getElementById('schedulePlayIn').onclick = function () {
  // Create a json object for variables to set.
  var variables = {};
  // Set the current time and date.
  var timeNow = new Date();
  variables['Time'] = timeNow.toLocaleTimeString();
  variables['Date'] = timeNow.toLocaleDateString();
  // Send the AnimateIn action with this input as the sender, and the new variable settings.
  ServiceHandler.scheduler.scheduleAction('animateIn', ServiceHandler.inputName, '', variables);
};
```

This creates a JavaScript object where each key is the name of an input variable and each value assigned to it is the string value for that Variable.

Then, it calls the `scheduleAction` API. [`scheduleAction`](/api-examples/sample-controllers-tour/01-schedule-action) is the core API function used to schedule “actions” on a title. Actions are special commands that manage title playback.

The parameters are:

| Parameter   | Description                                                                                                                                                          |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action`    | The action to take, for example ‘render’ to prepare a title or ‘update’ to update the title with new values.                                                         |
| `inputName` | Name of this Controller’s Input. If provided, the action applies to all Titles connected to this Input; if omitted, it applies to all Titles in the project.         |
| `titleId`   | The ID of a specific Title. If an Input is connected to multiple Titles, use this to specify which one to update (see the API on `scheduleCommand` to get Title IDs) |
| `variables` | The JavaScript object with any variable values to assign.                                                                                                            |

Take a look at the code for the other two buttons. It’s very similar, with the primary difference being the action itself.

> **Note**: The function calling `animateOut` doesn’t modify the variables, and instead sends an empty object to the scheduler (`{}`). Captivate will only modify variable values when it receives new data for them, so you only need to send variable values when data in Captivate should change.

Okay, now let’s install this and run it!

### Installing `ExampleApp.html` <a href="#installing-exampleapphtml" id="installing-exampleapphtml"></a>

Go back up to the top level and, if you are on Windows, simply double-click on copyfiles.bat. This script copies all XML, HTML and other related files in a sub-folder under `C:\Program Files\NewBlueFX\Titler Content\Resources\Service Handlers\HTML`

If on a Mac, you can drag and drop the entire Example App folder to `/Library/Application Support/NewBlueFX/Titler Content/Resources/Service Handlers/HTML`

### Run In Captivate <a href="#run-in-captivate" id="run-in-captivate"></a>

Start Captivate. If it is currently running, you must quit it and restart. This is because it only reads the XML definition file once, on startup.

Once it is running, place any title which has two or more text variables in the Title/Layer list.

Then, in the data controllers column of that title, click to select your new input from the popup menu.

<div align="center"><figure><img src="/files/FANm5KBpe7WJrYkYUKM3" alt=""><figcaption></figcaption></figure></div>

Once selected, it should show up as the input with the little green alien icon to the left of it.

Next, assign the `Date` and `Time` variables to your title in the preview pane.

<figure><img src="/files/ZEYixV3GlEW34sPbC83k" alt=""><figcaption></figcaption></figure>

Simply drag and drop each to the desired text fields in the title.

Now it is ready for testing. Try the three buttons.

### Debugging <a href="#debugging" id="debugging"></a>

Next, directly debug the app right here in the browser. Right mouse click to access the DevTools option.

<figure><img src="/files/1QFRBopBIuH9S8aXWDvZ" alt=""><figcaption></figcaption></figure>

This opens a Chromium debugger window.

Choose the sources tab and select the source file in the file view on the left.

<figure><img src="/files/l2t5bkHyyYaJPyfcQoYr" alt=""><figcaption></figcaption></figure>

Now you are ready to debug.

If you click to the left of a line number, it inserts a break point.

<figure><img src="/files/BrvvoivePImzEVouThY2" alt=""><figcaption></figcaption></figure>

Now, click on the button and the debugger will stop at this break point. You can now step through the code and see how it works.

If you make changes to the source code, it’s easy to update the code live in Captivate. Just copy the files in again and then right mouse click in the browser and choose reload.

<figure><img src="/files/Rcxe6AOONsexgIWw7wqL" alt=""><figcaption></figcaption></figure>

Now that you are up and running, go to [Sample Controllers Tour](/api-examples/sample-controllers-tour) and dive deeper into the programming samples. Be sure to check out the [JSON Command Tester](/api-examples/json-command-tester), which offers a quick way to discover and explore over two dozen API commands.


# Sample Controllers Tour

Now that you've gone through and created a simple controller, this page will show you additional samples to get your wheels turning on what is possible with the Captivate API.

This series of programming examples steps through common use cases. All are fully functional and the code is documented.

To get going, start by loading this embedded project.

{% file src="/files/pPpzvtSEKaxGpr039yD4" %}

This project includes all of the programming examples in the full tour so you can easily try them out.

The source code for all of these can be found at:

**Mac** `/Library/Application Support/NewBlue/Titler Content/Resources/Service Handlers/HTML/API Tour/tour/`

**Windows** `C:\Program Files\NewBlueFX\Titler Content\Resources\Service Handlers\HTML\API Tour\tour\`


# Schedule Action

This introduces the scheduleAction command and shows how the different actions work.

Choose an action, assign values to variables, and then execute the command. It also displays a description of each action.

```javascript
scheduler.scheduleAction(settings.action, inputName, '', settings.variables);
```

<figure><img src="/files/8Cvb3D4s5puEuaauiPRg" alt=""><figcaption></figcaption></figure>


# Subscribe for Notifications

This builds on the first project by adding an option to subscribe for title playback notifications.

<figure><img src="/files/bIzQEoHHnl2MuTZNneEC" alt=""><figcaption></figcaption></figure>

Notifications make it easy for your controller to always know the current state of the titles that it manages.

To implement, use the [`scheduleCommand`](/captivate-api/javascript-api-reference/using-the-servicehandler/schedulecommand-method) API call.

`scheduleCommand` is a general purpose API that supports a wide range of commands. In fact, even the `scheduleAction` call can be made through `scheduleCommand`.

It takes three parameters: the command name, a JavaScript object with all the parameters, and a JavaScript object with optional variable settings. Note that the last app in the tour, [JSON Command Tester](/api-examples/json-command-tester), provides a way to try all the commands and it documents all of the parameters for each.

```javascript
scheduler.scheduleCommand('subscribe', { input: inputName, events: 'play, data' }, {});
```

In this case, the command is “subscribe.” The parameter “input” instructs it to subscribe to all titles connected to this input, and the parameter “events” sets which behaviors to notify: “play” for playback status and “data” for changes in variables.

To receive notifications, you must also respond to [`onNotify`](/captivate-api/javascript-api-reference/using-the-servicehandler/signals) events from the scheduler.

```javascript
// Once subcriptions are enabled, listen for onNotify callbacks.
scheduler.onNotify.connect((notification) => {
  // Convert the payload string into a JavaScript object.
  let jsonReply = JSON.parse(notification);
  // And then use the object to inform the state...
});
```

\\


# Save and Load State

Continuing with this simple example, we add another useful capability - saving the state of the controller to the project, and loading it back when the project file is opened.

You may want to save the state of your controller directly in the project so the next time the user opens the project, any state information is reloaded.

To do this:

1. Keep all information that needs to be saved and loaded in one JavaScript object.
2. Whenever something changes in these settings, save it out using the [`saveSettings`](/captivate-api/javascript-api-reference/using-the-servicehandler/additional-methods-with-scheduler#savesettings-method) command.
3. On startup, retrieve the input settings using the [`loadSettings`](/captivate-api/javascript-api-reference/using-the-servicehandler/additional-methods-with-scheduler#loadsettings-method) command.

In this example, the JavaScript object that manages all of the state information is called “settings.” Saving the state the code looks like this:

```javascript
// Save this latest state in the project.
scheduler.saveSettings(inputName, JSON.stringify(settings));
```

This should be called any time the settings change, typically when the user makes a change in the UI.

To retrieve the settings, call `loadSettings` from the project when the `ServiceHandler` connects:

```javascript
// On startup, retrieve the input settings from the project.
scheduler.loadSettings(inputName, (projectSettings) => {
  // Convert into a JavaScript object.
  let newSettings = JSON.parse(projectSettings);
  // ... apply the settings to the UI
});
```


# Render a Preview

You might write a controller that needs to show what a title will look like with certain variable settings before going live with it.

The “[snapshot](/captivate-api/all-commands-reference/status-information#api-command-getsnapshot)” command does just that. With it, you can set variables and request a preview rendering.

This example shows how to use the snapshot command to preview:

```javascript
scheduler.scheduleCommand(
  'getSnapshot',
  { input: inputName, encode: 1, scaleToFit: 1, width: 800, height: 300, sync: 1 },
  settings.variables,
  (resultString) => {
    console.log(resultString);
  }
);
```

| Parameter            | Description                                                                                                                                    |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `encode`             | tells snapshot to return the png image as a byte-encoded string.                                                                               |
| `width` and `height` | set the size of the returned image.                                                                                                            |
| `scaleToFit`         | tells it to scale the image so it best fits the width and height, while maintaining the correct aspect ratio.                                  |
| `sync`               | tells snapshot to perform the work synchronously and return the data in the callback, otherwise, the data will come in a notification message. |

And then, once you like what you see, safely update the live title using the `scheduleAction` command:

```javascript
scheduler.scheduleAction('update', inputName, '', settings.variables);
```

<figure><img src="/files/kC1HCSoxmIjmhheGbDRJ" alt=""><figcaption></figcaption></figure>


# Curate a List of Titles

You might have a need to write a controller that can completely manage the full list of titles. It might need to access all of the titles in the list, understand their status, control their playback, and display what they look like.

For example, you might want to interface a hardware controller to control the playback of graphics or create a simple web app that runs on a browser elsewhere in the network, empowering a second person to drive all graphics playout.

<figure><img src="/files/iEET5cCOI7DbKrYOfnU3" alt=""><figcaption></figcaption></figure>

#### Retrieve the title data and icons <a href="#retrieve-the-title-data-and-icons" id="retrieve-the-title-data-and-icons"></a>

The [`getTitleControlInfo`](/captivate-api/all-commands-reference/status-information#api-command-gettitlecontrolinfo) command makes this very easy to do.

```javascript
scheduler.scheduleCommand('getTitleControlInfo', { icon: true, width: 100, height: 70 }, {}, (reply) => {
  let titleInfo = JSON.parse(reply);
  // ... Parse titleInfo and extract information and images for each title in the list.
});
```

| Parameter            | Description                                                 |
| -------------------- | ----------------------------------------------------------- |
| `icon`               | indicates this should return icons for all the titles.      |
| `width` and `height` | set the size of the returned icons.                         |
| `input`              | when set, only returns titles connected to the named input. |
| `id`                 | when set, only returns the one title listed by id.          |

[The returned payload is relatively extensive](/captivate-api/all-commands-reference/status-information#api-command-gettitlecontrolinfo), so the best way to find out what’s possible is to look at the code and run in the debugger. Capture the returned JavaScript object with a breakpoint or print it to the console to see all your options.

Or use the [`JSON Command Tester`](/api-examples/json-command-tester)which displays all returned objects too.

#### Subscribe for Notifications <a href="#subscribe-for-notifications" id="subscribe-for-notifications"></a>

Use the “[subscribe](/captivate-api/all-commands-reference/notifications#api-command-subscribe)” command. In this case, it is being applied individually for each title.

```javascript
scheduler.scheduleCommand('subscribe', { id: title.id, sender: cleanedId, events: 'play, control, data' },
```


# Multiple Behaviors

Another common scenario is a controller that connects into a specific data source or API and exhibits multiple functionalities.

For example, a social media controller might parse a chat and offer:

* A curated list of chat messages to select and show.
* A live poll compiled from votes in the chat.
* Special alerts that fly in and out when certain events occur.

Each of these could be connected to a different title, using different \variables, and intended for a different display purpose.

It doesn’t make sense to have a separate controller for each of these. Instead, one controller can log into the service, parse the data, and use it to drive these three different operations. So, it needs to present itself as a series of input instances, each with a different name, that share just one control UI.

In this example, “06a Multi Behavior” triggers an alert to one title, while “06b Multi Behavior” sends polling and image updates to another.

Here’s how it works:

#### XML Definitions for Shared Controller <a href="#xml-definitions-for-shared-controller" id="xml-definitions-for-shared-controller"></a>

Each input is represented by a separate XML file, but there are several attributes used to set the shared behavior.

The first input looks like this:

```xml
<inputBehavior
  name="API Tour: 06a Multi Behavior"
  url="tour/06_multiBehavior.html"
  iconOn="tour/tour_06.png"
  uiName="API Tour: 06 Multi Behavior"
  UIGroups="General"
>
  <variable name="Question" type="text" />
  <variable name="AnswerA" type="text" />
  <variable name="AnswerB" type="text" />
  <variable name="Image" type="image" />
</inputBehavior>
```

And the second looks like this:

```xml
<inputBehavior
  name="API Tour: 06b Multi Behavior"
  url="tour/06_multiBehavior.html"
  iconOn="tour/tour_06.png"
  uiName="API Tour: 06 Multi Behavior"
  UIGroups="General"
>
  <variable name="Question" type="text" tags="name" />
  <variable name="AnswerA" type="text" />
  <variable name="AnswerB" type="text" />
  <variable name="Image" type="image" />
  <variable name="PollA" type="graph" />
  <variable name="PollB" type="graph" />
</inputBehavior>
```

* The names are different. This ensures they show up as different inputs.
* The url, icon, and uiName are the same, because these are indeed managed by the same controller.
* The variables overlap, but they don’t have to. They could each drive a completely different set of variables.

#### Controller Code <a href="#controller-code" id="controller-code"></a>

The controller itself acts as a single source for two inputs.

In this case, it’s a UI that takes in information and uses it to drive alerts from input A and updates from input B.

<figure><img src="/files/mbKKPGXLCMVfxqtcLcoi" alt=""><figcaption></figcaption></figure>

The controller code cannot rely on `ServiceHandler.inputName` for the input name, because that will be registered only for the first input created. Instead, it needs to know the names as defined in the different XML files and use them directly.

```javascript
let inputBehavior1Name = 'API Examples: 6a Multi Behavior';
let inputBehavior2Name = 'API Examples: 6b Multi Behavior';
```

Then, it calls to `scheduleAction` (it should use whichever `inputName` is appropriate for the action).

The first input triggers alerts. This flies in the title, holds it for the requested duration, and then flies it back out.

```javascript
scheduler.scheduleAction('Alert', inputBehavior1Name, '', variables);
```

The second input triggers update actions. This assumes the title is already live and it is updating the displayed values with animation. So every time one of those sliders changes, it fills in the new value and calls:

```javascript
scheduler.scheduleAction('Update', inputBehavior2Name, '', variables);
```


# Curate a List of Items

Many controllers read a list of items and then manage their playout.

Examples include lists that are constantly updating such as chat messages or lists that are prepared such as a class list for a graduation.

A common need is to be able to parse a list, display it, and allow the user to pick items in the list and play them in.

This example reads random images from various artists and builds a list of them.

<figure><img src="/files/z93cnxw0FtfD76J6U0yA" alt=""><figcaption></figcaption></figure>

Take a look at the source code to see how it works.

Of particular interest:

#### Pre-Render <a href="#pre-render" id="pre-render"></a>

As each image is downloaded, a `scheduleAction` call is made to prepare that item so it will be ready to play whenever the user clicks the play button.

```javascript
scheduler.scheduleAction('render', inputName, '', {
  Name: jsonReply.author,
  Subtitle: jsonReply.url,
  Image: requestPicture.responseURL,
});
```

These renders will be cached in the project, so that they can be played in any order whenever they are needed.

#### Automatic Action <a href="#automatic-action" id="automatic-action"></a>

Each list item includes a “Play” button. Pressing this sends the “automatic” action to the `scheduler`. “automatic” is a natural choice for lists.

* If the title is not playing, “automatic” becomes “animateIn” and animates the Title in with the selected list item’s values.
* If the title is live with another list item, “automatic” becomes “update” and animates only the changes.
* If the title is live with the current list item, “automatic” becomes “animateOut” and animates the Title out.

```javascript
// When the user clicks play, use the automatic action
// which will animate in, animate out, or update,
// depending on Title play status and Variable values.
scheduler.scheduleAction('automatic', inputName, '', {
  Name: jsonReply.author,
  Subtitle: jsonReply.url,
  Image: requestPicture.responseURL,
});
```

This saves you from writing special state logic to track the status of the Titles and determine what action to take when the Play button is clicked.

#### Dynamic Input Definition <a href="#dynamic-input-definition" id="dynamic-input-definition"></a>

Although prior examples showed setting up variables in the XML configuration, they can also be set manually through the API using the `updateInputDefinition` command.

```javascript
// Redefine this input as having the three variables that we wish to use for this list.
scheduler.updateInputDefinition(ServiceHandler.inputName, {
  variables: {
    Name: { type: 'text', tags: 'name' },
    Subtitle: { type: 'text' },
    Image: { type: 'image' },
  },
});
```


# Vue Example - Weather Report

Up to this point, all examples are written in plain JavaScript without the use of any frameworks.

This example uses [Vue](https://vuejs.org/), a popular framework for building user interfaces. It builds on top of standard HTML, CSS and JavaScript and helps you efficiently develop user interfaces. It is well suited for the style of simple interfaces required for controllers. Keeping things simple, Vue apps will run from Captivate’s file server, so there is no need to set up a more sophisticated server of your own.

This example offers a simple weather report tool.

<figure><img src="/files/hGR17d362ll4uAzLII0n" alt=""><figcaption></figcaption></figure>

This provides a great working example for both building with Vue and using the National Weather Service APIs to capture weather data.


# Clocks and Counters

The Captivate engine can be very powerful for displaying multi-segment real time clocks, counters, and scores with independent animation of all the digits.

This example shows how to create a set of 5 different kinds of clocks and counters with relatively simple code.

<figure><img src="/files/7MRTlqrw1aRQUXRPQPK4" alt=""><figcaption></figcaption></figure>

Here’s how they work.

#### Writing a JavaScript Clock <a href="#writing-a-javascript-clock" id="writing-a-javascript-clock"></a>

The easiest way to build fast changing, high accuracy clocks and counters is to convert elapsed time into a formatted text string at a reasonably high refresh rate and update the display every time the string changes.

This approach also makes it easy to add specialized logic that is unique to the situation. For example, a countdown clock might switch to a different format, adding tenths of a second, when it reaches the last 10 seconds. Or, it might flip polarity and start counting up as overtime once it passes through 0. There are also great JavaScript libraries for converting time into all kinds of useful formats.

The logic looks something like this:

* On start, memorize the current time.
* Then, at very frequent intervals, calculate the elapsed time and convert it into a string in whatever format is desired.
* Whenever that string changes, schedule an update action with the new time value.

Here’s an example:

```javascript
// set up time constants in units from milliseconds
const second = 1000;
const minute = 60 * second;
const hour = 60 * minute;

// Initialize the clock string
let clockText = '';
let startTime = Date.now();

// Start the interval timer to check the time every 20 milliseconds
let clockID = setInterval(clockTimer, 20);

function clockTimer() {
  timeNow = Date.now();
  let elapsedTime = timeNow - startTime;
  // Code to convert the elapsed time into a formatted text string
  let ms = elapsedTime;
  let h = Math.floor(ms / hour).toString();
  ms = ms % hour;
  let m = Math.floor(ms / minute)
    .toString()
    .padStart(2, '0');
  ms = ms % minute;
  let s = Math.floor(ms / second)
    .toString()
    .padStart(2, '0');
  let newClockText = `${h}:${m}:${s}`;
  // If the time string actually changed, then send it.
  if (newClockText != clockText) {
    clockText = newClockText;
    // Send this new value to the Input.
    scheduler.scheduleAction('update', ServiceHandler.inputName, '', { Clock: clockText });
    // Also draw it in the web page
    document.getElementById('displayTime').innerHTML = clockText;
  }
}
```

#### Setting up the Pattern <a href="#setting-up-the-pattern" id="setting-up-the-pattern"></a>

In order for these clocks to run in real time with multiple animated digits and very little render overhead, the engine first builds a pre-rendered timeline of each individual digit in the clock.

For that to work, it must prepare all the possible values that each individual digit may require. Typically, this includes the numbers 0 through 9, but it may also include alphabetic characters or punctuation like ‘:’ or ‘.’.

To support this, a special pattern string defines all possible values for each digit of the variable.

The following is an example of a pattern for an hour:minute (or minute:second) clock that includes the “am” or “pm” at the end.

```javascript
let pattern = '[0-2][0-9]:[0-5][0-9][ap]m';
```

These are the rules for creating a pattern:

| Pattern | Interpretation                                         |
| ------- | ------------------------------------------------------ |
| `[]`    | denotes a character group                              |
| `-`     | inside a character group denotes a range of characters |
| `\`     | escape the next character and treat it as literal      |
| …       | all other characters are treated as literal.           |

> **Note 1**: When Captivate pre-renders a pattern, it creates a “cell” for each character in the pattern and generates animations for all possible values of that cell. Then, when it receives a value for that variable, it tries to fit the new data into the proper cells. To make this work reliably, always send Captivate data that fits the pattern promised by the Input even if that means adding extra space characters. For the above pattern, to send `"1:23pm"`, it’s recommended to actually set the variable to `" 1:23pm"` with a space character before the first digit.

> **Note 2**: Space characters are always considered a possibility for every cell and should not be specified separately, if you want to ensure a space character appears in your final output, use a different character of the proper width to identify the need for spacing.

Here’s an example to keep a space before the am/pm designation of a clock:

```javascript
let pattern = '[0-2][0-9]:[0-5][0-9][z][ap]m';
```

There are two ways to set the pattern:

In the XML descriptor for the variable:

```javascript
<inputBehavior name='API Tour: Clock Example' url='tour/clock.html' UIGroups='General'>
  <variable name='Clock 1' category='required' type='text'>
    <pattern>[0-9][0-9];[0-9][0-9].[0-9][0-9]</pattern>
  </variable>
  ...
</inputBehavior>
```

Or, via the `scheduler.updateInputDefinition` call:

```javascript
let variables = {
  Message: {
    type: 'text',
  },
  Clock: {
    pattern: '[0-9][0-9][:.][0-9][0-9]',
    type: 'text',
  },
};
// Update the definition of the clock pattern. This triggers a re-render.
scheduler.updateInputDefinition(ServiceHandler.inputName, { variables });
```

For this example, we use the second option because we actually have five different patterns to use, depending on which mode the user chooses.


# JSON Command Tester

A convenient way to discover and explore the API, with over two dozen commands.

The JSON Command Tester is built on the `scheduleCommand` function, and provides cut and paste fields to try out parameters. Once you’ve found and verified the functionality you need, you can even copy and paste from here into your own code. The JSON Command Tester can be accessed from the Data Controller column in your Captivate project panel under API Examples --> API Tour.

<figure><img src="/files/v8zJypiymrRu5m1pAhyf" alt=""><figcaption></figcaption></figure>

**Command**

Choose the command from the Command: drop down menu. Once selected, it displays information about the specific command, including details about all parameters.

This is the first parameter of the scheduleCommand() function. The second and third parameters are JavaScript objects that carry the attributes and variables for each command, respectively.

#### Parameters <a href="#parameters" id="parameters"></a>

The parameters box sets up the JavaScript object which carries all the attributes and options available for that command. To add or change values, edit directly in this.

Note that some parameters, are automatically filled in for convenience. These include the `input` name and title `id`.

#### Variables <a href="#variables" id="variables"></a>

If the particular command supports a set of variables, this displays a JavaScript object to manage the variables.

For convenience, it pre-fills the JavaScript object with all the variables of the connected title with their current values.

#### Execute Command <a href="#execute-command" id="execute-command"></a>

Once you have chosen a command and prepared the parameters and variables, click here to execute the command.

#### Returned <a href="#returned" id="returned"></a>

This displays the JavaScript object that is passed to the `scheduleCommand` callback as a JSON string.

#### Notification <a href="#notification" id="notification"></a>

Some commands, such as `subscribe` or `getSnapShot`, send JavaScript objects back later, via the `onNotify` callback (see above).


