Push and publish
Every write on this page needs a write key. Set LIGR_API_KEY in your shell and replace 203 with your
theme id.
The CLI sends requests to production by default. For another environment, set LIGR_API_URL to its
full REST base, including /rest/v2, for example https://<api-origin>/rest/v2. You can also pass
--base-url. The curl tutorials in Import through REST and Set up through REST read the same LIGR_API_URL.
npx ligr-graphic theme list or GET /v2/themes prints the themes your organization owns.
With the CLI
Section titled “With the CLI”ligr-graphic runs the calls below for you. See Quick start for the scaffold.
-
Create the theme. Once per theme. The command prints the theme id. In a folder that holds
graphic.json, it writes the theme id intoligr.json.--variables <file.json>adds theme variables from a JSON array of{ id, name, type, defaultValue }.Terminal npx ligr-graphic theme create --name "My Theme" --sports footballnpx ligr-graphic theme delete --theme 203 --yesremoves a theme you own and every graphic in it. A theme a competition still uses is refused. Remove the theme instance from the competition first. -
Create the graphic. Once per graphic. The command writes
ligr.jsonwith the theme id and the graphic id.Terminal npx ligr-graphic create --theme 203 --name "Scorebug" --sports football -
Push the bundle. The command builds, validates, uploads only the files whose hash changed, and records the bundle with
graphic.jsonas the manifest.Terminal npx ligr-graphic pushnpx ligr-graphic validateruns the same checks without a push.--skip-buildpushes the folder as it is. -
Publish the graphic, then the theme. The command publishes a graphic version. With
--theme-versionit also publishes a theme version pinned to it.Terminal npx ligr-graphic publish --theme-versionEach publish creates a new graphic version, even when nothing changed since the last one.
npx ligr-graphic statusprints the working version, the published versions, the assets and the lock. -
Inspect and activate the published theme version. Replace
12with the version printed above.Terminal npx ligr-graphic theme inspect --theme 203 --version 12npx ligr-graphic theme activate --theme 203 --version 12These commands select an existing snapshot. They do not publish another version or change its graphic selections.
-
Publish one theme version after several graphics. Publish each graphic without
--theme-version. Then publish one theme version that pins the latest published version of every graphic.Terminal npx ligr-graphic theme publish --theme 203 --dry-runnpx ligr-graphic theme publish --theme 203--dry-runprints the version number and the graphic versions, and writes nothing. The command prints the new version, each pinned graphic version, and whether the version is active. Add--activateto set it active. A graphic with no published version is not in the theme version.
Without the CLI
Section titled “Without the CLI”Send the key in Authorization: Bearer <api key>. The endpoints are under Code graphics
and Themes in the REST reference.
Create the theme and graphic once, then upload and publish each revision.
-
Create the theme. Once per theme. Keep the
idfrom the answer.GET /v2/themeslists the themes you own, andDELETE /v2/themes/{themeId}removes one that no competition uses.Terminal curl -X POST 'https://api.ligr.live/rest/v2/themes' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "My Theme", "sports": ["football"],"variables": [{ "id": "accent", "name": "accent", "type": "string", "defaultValue": "#ff0000" }] }'201 Created { "id": 203, "name": "My Theme", "activeVersion": null, "versions": [], "graphics": [] }The sixth theme returns
409 THEME_LIMIT_REACHED. -
Create the graphic. Once per graphic. Keep the
graphicIdfrom the answer.Terminal curl -X POST 'https://api.ligr.live/rest/v2/themes/203/code-graphics' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "name": "Scorebug", "sports": ["football"] }'201 Created {"graphicId": "e5515527-238e-44cb-9567-b65902218d47","id": 1761,"name": "Scorebug","type": "code","version": 0,"sports": ["football"],"publishedVersions": [],"lock": null}The answer holds two identifiers. Every later call in this guide takes
graphicId, the UUID, in the path.idis the internal row number; it never goes in a path. A path withidin it answers404.sportsdefaults to the sports of the theme. Leave outstateMachineto get the default manifest. You send the real one with the bundle. -
Request an upload URL for each file. One call opens one upload session.
Terminal curl -X POST "https://api.ligr.live/rest/v2/themes/203/code-graphics/$GRAPHIC_ID/uploads" \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "files": [{ "name": "index.html", "contentType": "text/html" },{ "name": "assets/app.js", "contentType": "application/javascript" }] }'200 OK {"uploadSessionId": "823d14c6-ef4e-4d72-932e-20d8ed422e3c","uploads": [{ "name": "index.html", "url": "https://…?X-Amz-Signature=…", "method": "PUT", "expiresInSeconds": 600 },{ "name": "assets/app.js", "url": "https://…?X-Amz-Signature=…", "method": "PUT", "expiresInSeconds": 600 }]}Keep the
uploadSessionId. Each URL is valid for 10 minutes. One request signs at most 200 files. For more, send the sameuploadSessionIdin the next request. The answer keeps that id, and every URL writes into the same session. The CLI does this for you. -
Send each file to its URL. Use the content type you declared.
Terminal curl -X PUT "$UPLOAD_URL" \-H 'Content-Type: text/html' \--data-binary @dist/index.html -
Record the bundle. Name every file the graphic keeps, with the manifest.
Terminal curl -X PUT "https://api.ligr.live/rest/v2/themes/203/code-graphics/$GRAPHIC_ID/bundle" \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{"uploadSessionId": "823d14c6-ef4e-4d72-932e-20d8ed422e3c","files": [{ "name": "index.html", "mime": "text/html", "size": 1240, "hash": "sha256:6f1c…" },{ "name": "assets/app.js", "mime": "application/javascript", "size": 8300, "hash": "sha256:9b02…" }],"stateMachine": { "schemaVersion": 1, "runtime": { "engine": "iframe-html", "entryFile": "index.html", "width": 1920, "height": 1080, "exitDurationMs": 800 }, "controlVariables": [], "userExpressions": [] }}'The answer is the working copy of the graphic, with its
assetsand itspublishedVersions. -
Publish the graphic version.
Terminal curl -X POST "https://api.ligr.live/rest/v2/themes/203/code-graphics/$GRAPHIC_ID/versions" \-H "Authorization: Bearer $LIGR_API_KEY"201 Created { "version": 3 } -
Publish a theme version that holds it.
Terminal curl -X POST 'https://api.ligr.live/rest/v2/themes/203/versions' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "activate": false }'201 Created { "version": 12, "activeVersion": 11 }Inspect
GET /v2/themes/203/versions/12, then select that exact snapshot:Terminal curl -X PUT 'https://api.ligr.live/rest/v2/themes/203/active-version' \-H "Authorization: Bearer $LIGR_API_KEY" \-H 'Content-Type: application/json' \-d '{ "version": 12 }'Reload the preview overlay to load the selected version. See Publish a theme version for pinning and rollback.
Upload sessions
Section titled “Upload sessions”The first POST …/uploads call opens an upload session. Further batches can extend that session by sending its uploadSessionId.
Every URL writes into that session, never directly into the graphic. A completed session cannot be reused.
PUT …/bundle moves the files of the one session you name into the graphic. It applies one rule
per file in files:
| The file is | Result |
|---|---|
| In the named session | The request copies it into the working copy |
| Not in the session, but the graphic already holds it | The request keeps it. Skip an unchanged file: compare its hash to the one in GET …/code-graphics/{graphicId} |
| In neither place | The request fails with CODE_GRAPHIC_INVALID and names the file in details[] |
A file you leave out of files is deleted. Leave out uploadSessionId only when every named file
is already in the graphic.
The bundle request clears the session it names when it succeeds. It leaves every other session alone. A publish never touches an upload session, so an upload URL you hold stays valid across a publish.
Record the bundle
Section titled “Record the bundle”files is the complete file set: name, mime, size in bytes and hash for every file the
graphic keeps. The API checks every size against the limits before it writes anything. The
hash is yours: the API stores it and returns it, so a later push can compare and skip an
unchanged file. sha256:<hex> is the usual form.
stateMachine is optional. Leave it out to keep the stored manifest. When you send it, the API
validates it against the file set of this request, so runtime.entryFile must be in files.
See The manifest.
Versions
Section titled “Versions”A publish turns the working copy into an immutable version and opens the next working copy. The
version in the answer is the number a theme version pins. GET …/code-graphics/{graphicId}
lists them in publishedVersions.
A theme version is a frozen list of graphic versions. POST /v2/themes/{themeId}/versions pins
every graphic you list at the version you give, and every graphic you do not list at its latest
published version. A graphic with no published version is left out. Overlays load the active theme version when their page loads.
Use GET …/versions/{version} to inspect a frozen selection and PUT …/active-version to activate or roll back to it.
The lock
Section titled “The lock”The dashboard editor takes a lock on a graphic while a person edits it. The lock stays for up to 60 seconds after the editor closes.
| Request | Lock behaviour |
|---|---|
POST …/uploads | Refuses to sign while another editor holds the lock. Does not take the lock, so a slow upload never blocks an editor |
PUT …/bundle | Holds the lock for the request |
POST …/versions | Holds the lock for the request |
A request that meets a lock answers 409 with code: "LOCKED" and holder.userName. Wait,
then retry. Do not delete the graphic and create it again. The API takes the lock as
API key <name>, and the same key can retake its own stale lock.
Errors
Section titled “Errors”| Status | Code | Cause |
|---|---|---|
| 400 | CODE_GRAPHIC_INVALID | The manifest breaks a rule, or a file in files is in neither the session nor the graphic. details[] names each problem |
| 400 | BUNDLE_TOO_LARGE | A file is over 5 MiB, or the bundle is over 10 MiB. details[] names the files |
| 400 | INVALID_ASSET_PATH | A file name holds .., an empty segment, a control character, or one of ?, #, % |
| 400 | GRAPHIC_TYPE_MISMATCH | The graphic is a Rive graphic, not a code graphic |
| 400 | — | The body fails schema validation. The answer has details per field |
| 403 | INSUFFICIENT_SCOPE | A read key on a write route |
| 404 | — | The theme or the graphic does not exist, or your organization does not own it |
| 409 | LOCKED | Another editor holds the lock |
| 409 | GRAPHIC_ASSET_MISSING | A published asset or thumbnail is absent from storage. Upload the missing file, then publish again |
| 409 | GRAPHIC_ASSET_INVALID | An asset cannot be read, or its size or SHA-256 differs. Replace the file, then publish again |
See Errors for the full map.
Request count
Section titled “Request count”An upload batch is one request: POST …/uploads signs every file in one call. A full push of a
graphic is four requests. See Rate limits.