Import through REST
This tutorial uses a dedicated example theme. It does not reuse or activate an unrelated production theme.
It creates a theme, graphic, graphic version, theme snapshot, control room, section, and preset. Overlay setup also needs your test match and overlay IDs.
Use a write API key with themes:read and themes:write.
Keep it in LIGR_API_KEY; never print it.
1. Prepare the terminal
Section titled “1. Prepare the terminal”export LIGR_API_URL="${LIGR_API_URL:-https://api.ligr.live/rest/v2}"
api() { method=$1 path=$2 shift 2 curl --fail-with-body --silent --show-error \ -X "$method" "$LIGR_API_URL/$path" \ -H "Authorization: Bearer $LIGR_API_KEY" \ -H 'Content-Type: application/json' "$@"}Set LIGR_API_URL once. The CLI reads the same variable.
The default REST base URL is https://api.ligr.live/rest/v2.
Each api call on these pages takes a path relative to that base, for example themes.
The API key and theme must belong to the same organization.
Check capability before uploading source:
THEME=$(api POST themes --data '{"name":"Rive REST tutorial","sports":["football"]}')THEME_ID=$(jq -er '.id' <<< "$THEME")api GET "themes/$THEME_ID/rive-graphics/capabilities" | jqIf sourceImports is false, source preparation is unavailable in this environment.
Compile externally and upload .riv when runtime inspection remains available.
2. Load the downloaded example
Section titled “2. Load the downloaded example”Complete Local RML scorebug project in the same terminal first. That guide downloads the public ZIP and configuration, then exports their absolute paths.
: "${PROJECT:?Complete the local RML project guide first}": "${UPLOAD:?Complete the local RML project guide first}": "${CONFIG:?Complete the local RML project guide first}"test -f "$PROJECT/rive.yaml"test -f "$UPLOAD"test -f "$CONFIG"SIZE=$(wc -c < "$UPLOAD" | tr -d ' ')SHA256=$(shasum -a 256 "$UPLOAD" | awk '{print $1}')3. Create and complete the upload
Section titled “3. Create and complete the upload”Choose source retention now. This example sends explicit false.
Supplied playback images are preserved even when the original archive is not retained.
UPLOAD_SESSION=$(api POST "themes/$THEME_ID/rive-graphics/uploads" --data "$(jq -n \ --arg filename continental-scorebug.zip \ --argjson size "$SIZE" \ --arg sha256 "$SHA256" \ '{filename: $filename, size: $size, sha256: $sha256, retainSource: false}')")
JOB_ID=$(jq -er '.jobId' <<< "$UPLOAD_SESSION")CANDIDATE_ID=$(jq -er '.candidateId' <<< "$UPLOAD_SESSION")UPLOAD_URL=$(jq -er '.url' <<< "$UPLOAD_SESSION")UPLOAD_CHECKSUM=$(jq -er '.headers["x-amz-checksum-sha256"]' <<< "$UPLOAD_SESSION")UPLOAD_HEADERS=$(mktemp)
curl --fail-with-body --silent --show-error -D "$UPLOAD_HEADERS" -o /dev/null \ -X PUT "$UPLOAD_URL" \ -H "Content-Length: $SIZE" \ -H "x-amz-checksum-sha256: $UPLOAD_CHECKSUM" \ --data-binary "@$UPLOAD"
VERSION_ID=$(awk 'BEGIN{IGNORECASE=1} /^x-amz-version-id:/{gsub("\\r", "", $2); print $2}' "$UPLOAD_HEADERS")test -n "$VERSION_ID"rm "$UPLOAD_HEADERS"Send Content-Length and every header returned in the upload-session headers map.
The signature requires the checksum header, and storage rejects a body that does not match it.
Send the opaque x-amz-version-id from the completed upload to preparation.
api POST "themes/$THEME_ID/rive-graphics/imports/$JOB_ID/prepare" \ --data "$(jq -n --arg versionId "$VERSION_ID" '{versionId: $versionId}')" | jq4. Poll and select
Section titled “4. Poll and select”while :; do STATUS=$(api GET "themes/$THEME_ID/rive-graphics/imports/$JOB_ID") STATE=$(jq -er '.status' <<< "$STATUS") printf '%s\n' "$STATE" case "$STATE" in ready) break ;; selection-required) jq '.candidates' <<< "$STATUS" : "${SELECTED_ENTRY:?Set SELECTED_ENTRY to an exact entry returned in candidates}" jq -e --arg entry "$SELECTED_ENTRY" '.candidates | any(.entry == $entry)' <<< "$STATUS" >/dev/null api POST "themes/$THEME_ID/rive-graphics/imports/$JOB_ID/selection" \ --data "$(jq -n --arg entry "$SELECTED_ENTRY" '{entry: $entry}')" >/dev/null ;; failed|cancelled|expired) jq '.error' <<< "$STATUS"; exit 1 ;; esac sleep 2done
jq '{metadata, runtime, preview}' <<< "$STATUS"Use an entry exactly as returned in candidates.
This archive has one project. For another archive, select its returned entry explicitly before continuing.
A ready result means preparation completed. It does not mean every external image has been supplied.
Read unresolved asset status and optional default image dimensions before attachment:
jq '.metadata.assets[] | select(.unresolved)' <<< "$STATUS"jq '.metadata.viewModels[].defaultInstanceBindings[]? | select(.type == "image") | {path, pathSegments, imageDimensions}' <<< "$STATUS"A draft can contain unresolved external images. Resolve them through image uploads or bindings in the builder. Preparation does not fetch external image URLs. Missing dimensions remain absent; do not substitute the artboard size.
5. Configure and create the working graphic
Section titled “5. Configure and create the working graphic”The inspected fixture uses artboard index 0 and state-machine index 0.
Its separate configuration binds live football data and the stage control variable.
The local RML project holds the full selection and
configuration schema: artboardIndex, stateMachineIndex, controlVariables and bindings.
Bind the visibility of the graphic to the reserved hide variable, as the example does with
{ "id": "Main.on", "expression": "!$v.hide.value" }. The server sets hide on every show and
hide command. The server adds hide to controlVariables and lists it there. Do not change or delete it.
A graphic that binds visibility to some other variable still accepts those commands and answers 200.
That graphic never appears or disappears. See How it works.
CREATE_BODY=$(jq -n --slurpfile config "$CONFIG" \ '{name: "Continental scorebug", selection: $config[0].selection, configuration: $config[0].configuration}')
GRAPHIC=$(api POST "themes/$THEME_ID/rive-graphics/candidates/$CANDIDATE_ID/graphics" \ --data "$CREATE_BODY")GRAPHIC_ID=$(jq -er '.graphicId' <<< "$GRAPHIC")ASSET_ID=$(jq -er '.assetId' <<< "$GRAPHIC")
api GET "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID" | jqapi GET "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/files/metadata?version=working&assetId=$ASSET_ID" | jqReplace an existing Rive asset
Section titled “Replace an existing Rive asset”Use the same preparation and attachment endpoints for replacement.
Set GRAPHIC_ID to your existing graphic and UPLOAD to the new .rev, .riv, or project ZIP.
Read the working draft before creating the upload session.
WORKING=$(api GET "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID")OLD_ASSET_ID=$(jq -er '[.assets[] | select(.type == "rive")] | if length == 1 then .[0].id else error("Select one Rive asset ID explicitly") end' <<< "$WORKING")UPDATED_AT=$(jq -er '.updatedAt' <<< "$WORKING")SIZE=$(wc -c < "$UPLOAD" | tr -d ' ')SHA256=$(shasum -a 256 "$UPLOAD" | awk '{print $1}')
UPLOAD_SESSION=$(api POST "themes/$THEME_ID/rive-graphics/uploads" --data "$(jq -n \ --arg filename "$(basename "$UPLOAD")" --argjson size "$SIZE" --arg sha256 "$SHA256" \ --arg graphicId "$GRAPHIC_ID" --arg assetId "$OLD_ASSET_ID" --arg updatedAt "$UPDATED_AT" \ '{filename: $filename, size: $size, sha256: $sha256, retainSource: false, target: {graphicId: $graphicId, assetId: $assetId, updatedAt: $updatedAt}}')")Continue step 3 from the JOB_ID assignment, then complete step 4.
The API acquires its own temporary editing lock. Do not send a dashboard lock token.
Conversion does not hold that lock; attachment acquires it again and verifies the original target timestamp and asset.
Inspect the prepared metadata and review your selection and binding overrides before attachment.
Use the step 5 attachment endpoint and record its returned assetId; replacement creates a new runtime asset ID.
Send only bindings, inputs and assets on a replacement. Control variables, user expressions and layout are edited through the operation routes; a replacement that includes them is refused with 422 INVALID_CONFIGURATION. See Edit through REST.
Submit reviewed bindings, inputs, and assets overrides for the incoming metadata instead.
Compatible bindings retain their configured exposure sizes, including values that you explicitly cleared.
The incoming runtime refreshes intrinsic imageDimensions; those dimensions do not overwrite your exposure settings.
LOCKED means another editor currently owns the graphic. Retry after that editor releases it.
TARGET_CHANGED means the saved draft changed during preparation. Read the new draft and start a new replacement import.
If the attachment response is lost, retry the same candidate with the identical request body.
An already completed attachment returns the same graphic and asset IDs.
Replacement changes only the working draft. Earlier published versions retain their exact runtime, playback images, configuration, and any retained source files. Publish and activate separately when the replacement is ready.
6. Publish, snapshot, and activate
Section titled “6. Publish, snapshot, and activate”GRAPHIC_VERSION=$(api POST "themes/$THEME_ID/rive-graphics/$GRAPHIC_ID/versions" | jq -er '.version')
THEME_VERSION=$(api POST "themes/$THEME_ID/versions" --data "$(jq -n \ --arg graphicId "$GRAPHIC_ID" --argjson version "$GRAPHIC_VERSION" \ '{graphics: [{graphicId: $graphicId, version: $version}], activate: false}')" | jq -er '.version')
api PUT "themes/$THEME_ID/active-version" \ --data "$(jq -n --argjson version "$THEME_VERSION" '{version: $version}')" | jqThese are three separate mutations. Activation changes what new overlay page loads will render.
The theme version also pins every graphic you do not list in graphics.
Each of those graphics is pinned at its latest published version.
7. Create the control-room preset
Section titled “7. Create the control-room preset”ROOM=$(api POST control-rooms --data "$(jq -n --argjson themeId "$THEME_ID" \ '{themeId: $themeId, name: "Continental scorebug room"}')")ROOM_ID=$(jq -er '.id' <<< "$ROOM")
SECTION=$(api POST "control-rooms/$ROOM_ID/sections" --data '{"name":"Native graphics"}')SECTION_ID=$(jq -er '.id' <<< "$SECTION")
PRESET=$(api POST "control-rooms/$ROOM_ID/presets" --data "$(jq -n \ --arg sectionId "$SECTION_ID" --arg graphicId "$GRAPHIC_ID" \ '{sectionId: $sectionId, graphicId: $graphicId, name: "Continental scorebug", variableValues: {stage: "GROUP A"}}')")PRESET_ID=$(jq -er '.id' <<< "$PRESET")Create a test match, theme profile, and manual overlay through Set up through REST.
Use this theme and room when that guide requests themeId and defaultControlRoomId.
api POST "overlays/$OVERLAY_ID/control-room/graphics" --data "$(jq -n \ --argjson presetId "$PRESET_ID" \ '{action: "show", presetId: $presetId, variableValues: {stage: "ROUND OF 16"}}')" | jqThe show override changes Main.stage. Match facts remain authoritative for bindings under $d.
This call fires a preset, so it carries the values an operator configured on that preset.
To fire the graphic without a preset, and send every value yourself, use
Graphics commands:
POST /v2/overlays/{overlayId}/graphics/commands takes command with graphicUuid or name
instead of action with presetId. Both endpoints write the same state. Pick the preset endpoint
when an operator owns the values, and the command endpoint when your own system does.
Use POST /imports/{jobId}/cancel for an unattached import you will not use.
Read Source files and privacy before retaining or downloading originals.