Skip to content

Set up through REST

Create the resources through REST, then open the returned control-room URL. This flow supports code graphics and native Rive graphics.

You need an organization and an API key. The key needs themes:write, competitions:write, teams:write, venues:write, matches:write, and overlays:write. Each write scope also allows reads for that resource. The person opening the control room needs an authenticated dashboard session with access to the same organization.

Follow Publish without the CLI to create the theme, upload the bundle, and publish the graphic. For native Rive, follow Import through REST through theme activation. Publish a theme version, then activate that exact version through REST. Save the theme’s numeric id as THEME_ID and the graphic’s UUID as GRAPHIC_ID.

OperationRoute
Create the themePOST /v2/themes
Create the graphicPOST /v2/themes/{themeId}/code-graphics
Request signed upload URLsPOST /v2/themes/{themeId}/code-graphics/{graphicId}/uploads
Upload filesPUT each returned upload URL with the file body
Record the files and manifestPUT /v2/themes/{themeId}/code-graphics/{graphicId}/bundle
Publish the graphicPOST /v2/themes/{themeId}/code-graphics/{graphicId}/versions
Publish the theme snapshotPOST /v2/themes/{themeId}/versions
Activate that snapshotPUT /v2/themes/{themeId}/active-version

The remaining examples use Bash, curl, and jq. Run them in one terminal. Set LIGR_API_KEY, THEME_ID, and GRAPHIC_ID from your authentication and publishing steps.

Terminal
set -euo pipefail
export LIGR_API_URL="${LIGR_API_URL:-https://api.ligr.live/rest/v2}"
export LIGR_REST="${LIGR_API_URL%/v2}"
api() {
local method="$1" route="$2"
shift 2
curl --fail-with-body --silent --show-error \
-X "$method" "$LIGR_REST/$route" \
-H "Authorization: Bearer $LIGR_API_KEY" \
-H 'Content-Type: application/json' "$@"
}

Set LIGR_API_URL to your target environment before continuing. The Rive tutorial and the CLI read the same variable. The default is https://api.ligr.live/rest/v2. This page calls v1 and v2 routes, so LIGR_REST removes the /v2 suffix.

A room belongs to your organization and one theme. A section groups its preset buttons.

Terminal
ROOM_ID=$(api POST v2/control-rooms --data "$(jq -n \
--argjson themeId "$THEME_ID" \
'{themeId: $themeId, name: "Match control room"}')" | jq -er '.id')
SECTION_ID=$(api POST "v2/control-rooms/$ROOM_ID/sections" \
--data '{"name":"Match graphics"}' | jq -er '.id')
PRESET_ID=$(api POST "v2/control-rooms/$ROOM_ID/presets" --data "$(jq -n \
--arg sectionId "$SECTION_ID" --arg graphicId "$GRAPHIC_ID" \
'{sectionId: $sectionId, graphicId: $graphicId, name: "Scorebug", variableValues: {}}')" \
| jq -er '.id')

The graphic must appear in the theme’s active published version. Preset creation reads that published manifest, including its variable types and defaults. Only variables named in variableValues or exposeVariables appear as editable fields on the preset. The API stores the internal preset structure for you.

Set variableValues by manifest variable name, for example {"CustomText":"Centre Court"} when your graphic defines CustomText. variableValues accepts string, number, boolean and enum variables. Name fact, stat, team, player and match variables in exposeVariables; the preset shows them and the operator picks the value live. Do not send hide; the show and hide actions control visibility. Unknown names and invalid values fail before the API creates the preset.

Expose an event picker and a statistic picker
api POST "v2/control-rooms/$ROOM_ID/presets" --data "$(jq -n \
--arg sectionId "$SECTION_ID" --arg graphicId "$GRAPHIC_ID" \
'{sectionId: $sectionId, graphicId: $graphicId, name: "Scorebug",
variableValues: {AddedTime: "0"}, exposeVariables: ["Event", "SingleStat"]}')" | jq

The response lists exposedVariables: every variable the operator can see, with or without a stored value. To change a preset later, send PUT /v2/control-rooms/{roomId}/presets/{presetId}. Send name or sectionId alone to rename or move it. Send variableValues and exposeVariables together to replace its variable set; the stored set becomes exactly what you send.

To reuse resources, list rooms with GET /v2/control-rooms?themeId={themeId}. List their sections and presets with GET /v2/control-rooms/{roomId}/sections and GET /v2/control-rooms/{roomId}/presets.

3. Create the competition, venue, and teams

Section titled “3. Create the competition, venue, and teams”

Discover competitions through GET /v1/competitions and venues through GET /v1/venues. List a competition’s teams with GET /v1/competitions/{competitionId}/teams. Use the returned IDs to skip the corresponding creation calls.

List grades through REST. Set GRADE_ID to the returned ID that matches your competition. The example uses adult mixed football; change the metadata for your competition.

Terminal
api GET v1/competitions/grades | jq .
# Set GRADE_ID to the selected numeric id before continuing.
COMPETITION_ID=$(api POST v1/competitions --data "$(jq -n \
--argjson gradeId "$GRADE_ID" \
'{name: "My competition", sport: "football", age: "Adults", gender: "Mixed", gradeId: $gradeId}')" \
| jq -er '.id')
VENUE_ID=$(api POST v1/venues --data '{"name":"Main ground"}' | jq -er '.id')
create_team() {
api POST v1/teams --data "$(jq -n \
--arg name "$1" --arg abbreviation "$2" \
--argjson gradeId "$GRADE_ID" --argjson competitionId "$COMPETITION_ID" \
--argjson venueId "$VENUE_ID" \
'{name: $name, abbreviation: $abbreviation, sport: "football", age: "Adults", gender: "Mixed",
gradeId: $gradeId, competitionIds: [$competitionId], defaultVenueId: $venueId}')" | jq -er '.id'
}
HOME_TEAM_ID=$(create_team 'Home team' HOME)
AWAY_TEAM_ID=$(create_team 'Away team' AWAY)

Graphics show the team logo as logoUrl. Upload the logo in two calls. The API does not fetch images from a URL.

Use a PNG or JPEG file of 500 KB or less. Declare its type, size, and SHA-256 first. The response holds uploadId, a signed url, and the headers for the upload.

Terminal
LOGO=home-crest.png
SESSION=$(api POST "v1/teams/$HOME_TEAM_ID/logo/uploads" --data "$(jq -n \
--argjson size "$(wc -c < "$LOGO" | tr -d ' ')" \
--arg sha256 "$(shasum -a 256 "$LOGO" | cut -d ' ' -f 1)" \
'{contentType: "image/png", size: $size, sha256: $sha256}')")
curl --fail-with-body --silent --show-error -X PUT "$(jq -er '.url' <<< "$SESSION")" \
-H "x-amz-checksum-sha256: $(jq -er '.headers["x-amz-checksum-sha256"]' <<< "$SESSION")" \
--data-binary "@$LOGO"
api PUT "v1/teams/$HOME_TEAM_ID/logo" --data "$(jq -n \
--argjson uploadId "$(jq -er '.uploadId' <<< "$SESSION")" '{uploadId: $uploadId}')" | jq '{id, logoUrl}'

Do the PUT before expiresAt. Send the uploadId within one hour. The API checks the size, the SHA-256, and the image type, then stores the logo. Each upload sets a logo one time. To change the logo, open a new upload. To remove the logo, send DELETE /v1/teams/{teamId}/logo. A team without a logo shows the club logo, if the club has one.

Set MATCH_DATE to your kickoff time in ISO 8601 format, including its timezone. The example uses team competitors. Other sports can use player competitors. competitorsType is the match format: singles, doubles, teams or group. entityType is the kind of each row in competitors: team or player. For example, a doubles match has competitorsType: doubles and four entityType: player rows. A group match also has player rows.

Terminal
MATCH_ID=$(api POST v2/matches --data "$(jq -n \
--arg date "$MATCH_DATE" --argjson competitionId "$COMPETITION_ID" \
--argjson venueId "$VENUE_ID" --argjson home "$HOME_TEAM_ID" --argjson away "$AWAY_TEAM_ID" \
'{competitionId: $competitionId, venueId: $venueId, date: $date,
competitorsType: "teams", competitors: [
{entityId: $home, entityType: "team", meta: {isHome: true}},
{entityId: $away, entityType: "team", meta: {isHome: false}}
]}')" | jq -er '.id')
PROFILE_ID=$(api POST "v2/competitions/$COMPETITION_ID/theme-profiles" --data "$(jq -n \
--argjson themeId "$THEME_ID" --argjson roomId "$ROOM_ID" \
'{themeId: $themeId, name: "Broadcast profile", defaultControlRoomId: $roomId}')" | jq -er '.id')

The theme must support the competition’s sport and have an active published version. The first profile becomes the competition default. This flow also assigns the profile and room explicitly to the overlay.

To set competition-specific colors or labels, include themeVariableValues when creating the profile. Read variables from GET /v2/themes/{themeId}/versions/{activeVersion}. Use those variable IDs, for example [{"variableId":"accent","value":"#0055ff"}]. The response returns the saved values. Unknown IDs and invalid types fail before creation.

To reuse a profile, call GET /v2/competitions/{competitionId}/theme-profiles.

Terminal
OVERLAY=$(api POST v1/overlays --data "$(jq -n \
--argjson matchId "$MATCH_ID" --argjson profileId "$PROFILE_ID" --argjson roomId "$ROOM_ID" \
'{name: "Broadcast overlay", matchId: $matchId, competitionThemeSettingId: $profileId,
controlRoomId: $roomId, autoMode: false, adType: "noBrands"}')")
OVERLAY_ID=$(jq -er '.id' <<< "$OVERLAY")
CONTROL_ROOM_URL=$(jq -er '.controlRoomUrl' <<< "$OVERLAY")
api GET "v1/overlays/$OVERLAY_ID" | jq '{id, autoMode, adType, competitionThemeSettingId, controlRoomId, controlRoomUrl}'
printf '%s\n' "$CONTROL_ROOM_URL"

The read response confirms the saved profile, room, and manual mode. controlRoomUrl includes the organization, match, overlay, and room IDs. It opens the saved setup without a theme-profile editor or overlay-settings visit.

autoMode: false requires adType: "noBrands" or "brands". The API rejects manual setup with "free". An update preserves fields you omit, including the ad type.

Open CONTROL_ROOM_URL in your authenticated browser. Select GFX In on the preset and check the preview. If the preset exposes variables, edit them and select Update GFX. Select GFX Out to hide it.

You can also fire the same prepared preset through REST:

Terminal
api POST "v2/overlays/$OVERLAY_ID/control-room/graphics" --data "$(jq -n \
--argjson presetId "$PRESET_ID" '{action: "show", presetId: $presetId}')"

7. Check a prepared room without a dashboard login

Section titled “7. Check a prepared room without a dashboard login”

Use this procedure when you cannot open the dashboard, for example from an automation. The key needs themes:read to read the room, overlays:read to see its overlays, and overlays:write to fire presets.

  1. Read the room. The response holds the theme, the sections, and every preset with its graphicId, graphicName and exposedVariables.

    Terminal
    ROOM=$(api GET "v2/control-rooms/$ROOM_ID")
    jq '{theme, presets: [.presets[] | {id, name, graphicName, exposedVariables}]}' <<< "$ROOM"
  2. Open the monitoring view of the overlay. Read monitoringUrl from the room; do not build it. The room lists overlays only when the key has overlays:read.

    Terminal
    jq -er --argjson id "$OVERLAY_ID" '.overlays[] | select(.id == $id) | .monitoringUrl' <<< "$ROOM"
  3. Fire each preset and check the monitoring view after each command.

    Terminal
    for PRESET in $(jq -r '.presets[].id' <<< "$ROOM"); do
    api POST "v2/overlays/$OVERLAY_ID/control-room/graphics" --data "$(jq -n \
    --argjson presetId "$PRESET" '{action: "show", presetId: $presetId}')" > /dev/null
    read -r -p "Preset $PRESET on air? Press Enter to continue."
    done
  4. Update a preset that is on air. Send the values to change in variableValues.

    Terminal
    api POST "v2/overlays/$OVERLAY_ID/control-room/graphics" --data "$(jq -n \
    --argjson presetId "$PRESET_ID" '{action: "update", presetId: $presetId, variableValues: {CustomText: "Court 2"}}')"
  5. Hide each preset.

    Terminal
    api POST "v2/overlays/$OVERLAY_ID/control-room/graphics" --data "$(jq -n \
    --argjson presetId "$PRESET_ID" '{action: "hide", presetId: $presetId}')"

See Check what is on screen if a graphic does not appear.

Publishing another theme version does not change the active version unless you request activation. Use version inspection, activation, and rollback through REST. Reload existing overlay pages after changing the active version.