Skip to content
Ask your AI agent

Data API

Push live JSON into a workspace and bind nested fields onto graphics. Designed for low-latency feeds — esports stats, score tickers, anything your own system can shape and emit. Sub-1-second from POST to graphic re-render on CG-SDK and OGraf graphics.

Polling-based sources

If your data lives in a Google Sheet, a CSV/JSON URL, or an Excel file, the poll-based data sources are the simpler fit. Use the Data API when your system pushes the data and update latency matters.

When to use

ScenarioUse
Operator updates a Google Sheet manually during the showGoogle Sheets
Stats provider exposes a CSV / JSON URL we can poll on a scheduleCSV / JSON URL
Operator updates an Excel file on their machineExcel
Your system has live JSON it can push to usData API
Your data updates several times per secondData API

The Data API inverts the usual flow — Lumora is a sink, your external system drives writes. No polling cadence to tune; updates land as fast as you push them.

Authentication

Push requests use a workspace Server API key on the standard Authorization header:

http
Authorization: Bearer lumora_live_sk_<43 chars>

The Data API doesn't introduce a new key type. Mint a key once from SettingsAPI Keys and reuse it across server-side triggers, refreshes, and pushes. The same scope rules apply: a key only works against the workspace it was minted in.

JWTs are rejected

This endpoint is API-key-only. A signed-in browser session (Supabase JWT) gets a 401. The control panel uses JWTs to manage data sources, but pushes must come from a server with a long-lived API key.

Setting up a data source

  1. In the control panel, open the Data Sources modal (cog icon next to Refresh Data Sources).
  2. Click + Data API.
  3. Fill in:
    • Name — display label, free-form.
    • Slug — appears in the push URL. Lowercase letters, numbers, dashes; 1–50 characters; no leading or trailing dash.
    • Sample payload (optional) — paste an example of the JSON your system will send. The binding picker uses it to render a clickable tree, so you can build bindings before the first real push lands. Persists with the source.
  4. Save. The modal shows the push URL and a copy button.

Renaming the slug later is safe — bindings reference the source by id internally.

Endpoint

http
POST https://<workspace>.app.lumora.live/api/v1/data/<slug>
MethodPOST only. No GET, PUT, or DELETE.
AuthAuthorization: Bearer lumora_live_sk_<43 chars>
Content-TypeMust include application/json
BodyAny JSON value (object, array, primitive). 1 MB max.

Parameters

NameWhere to find it
<workspace>Your workspace's subdomain — the part before .app.lumora.live in the URL when you're signed in.
<slug>The slug you chose when creating the data source. Visible in the Data Sources modal.

Responses

Success

json
{ "ok": true }

Error

json
{ "error": "Invalid JSON" }

Status codes

CodeMeaning
200Push accepted. The cached payload is updated and bound graphics re-render.
400Invalid JSON, payload over 1 MB, or non-application/json Content-Type.
401Missing/invalid API key, or this endpoint was called with a session JWT instead of an API key.
403The API key's workspace doesn't match the URL's workspace.
404No data source with this slug in this workspace.
429Burst or daily rate limit exceeded. The previous cached value continues to serve graphics.

A 404 after a slug rename or deletion is expected — bindings still resolve internally by id. Update your push target to the new slug.

Examples

Push a simple payload

bash
curl -X POST \
  -H "Authorization: Bearer lumora_live_sk_<your key>" \
  -H "Content-Type: application/json" \
  -d '{"match": {"score": {"home": 1, "away": 0}, "minute": 32}}' \
  https://your-workspace.app.lumora.live/api/v1/data/match-stats

Push from Node

js
const url = `https://${workspace}.app.lumora.live/api/v1/data/${slug}`

await fetch(url, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LUMORA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ match: { score: { home: 1, away: 0 }, minute: 32 } }),
})

Push from Python

python
import os, requests

url = f"https://{workspace}.app.lumora.live/api/v1/data/{slug}"

requests.post(
    url,
    headers={
        "Authorization": f"Bearer {os.environ['LUMORA_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={"match": {"score": {"home": 1, "away": 0}, "minute": 32}},
)

Binding graphics to the payload

Once a push has landed (or you've pasted a sample payload), you can bind graphic fields to nested values.

  1. In the graphic editor, click Data Bindings.
  2. For a bindable element, click Link.
  3. In the Data Source dropdown, pick your Data API source.
  4. The latest payload renders as a collapsible JSON tree. Click any leaf to insert its path. The path also accepts free-text editing — the picker live-validates the syntax.
  5. Link to this Path to confirm.

The bound graphic field updates whenever a new payload arrives.

Path syntax

Dot-separated keys with bracket array indexing:

text
match.score
teams[0].players[2].name
events[5].timestamp
[0].name                     # payload root is itself an array
grid[3][7]                   # consecutive brackets
event-type                   # hyphens permitted in keys
player_name.first_name       # underscores permitted

Allowed in keys: [A-Za-z_][A-Za-z0-9_-]*. Indices: non-negative integers. No wildcards, no filters, no recursive descent — preprocess on your side if you need aggregates.

A path that doesn't resolve in the current payload renders as the graphic's SDK default (or blank if no default was declared). There's no error — bindings tolerate temporary shape gaps.

Behaviour

  • Latest value only. Each push overwrites the previous payload. There's no history or queue.
  • Persistence. The latest payload is persisted to the database every 10 seconds when it has changed. Quiet sources don't write. The cache survives a server restart.
  • No diff suppression. Every push triggers a graphic re-render and a socket fan-out, even if the payload is identical to the last one.
  • Lottie graphics. Live updates apply on the next take-on (lockstep mode), the same as other data sources. Sub-1-second latency holds for CG-SDK and OGraf only.
  • Over-quota behaviour. A 429 rejects the write — the previous cached payload keeps serving graphics. Pushes resume cleanly when the window resets.

Rate limits

The Data API shares the workspace's existing API rate limit budget — see Server API rate limits. Both the per-minute burst and the rolling-24h daily ceiling apply on every push. Whichever runs out first triggers a 429 Too Many Requests with a Retry-After header.

For high-frequency feeds (e.g. live esports stats arriving multiple times per second), pick a plan that comfortably covers your push rate.

Deleting a source

Deleting a data source removes it permanently. The confirmation dialog tells you how many graphic fields and graphics will be unlinked first. On confirm:

  • the binding entry is removed from each affected graphic
  • the resolved value is cleared from the graphic's data so the SDK default re-asserts
  • on-air graphics revert immediately
  • the cached payload and database row are deleted

A renaming flow doesn't unlink anything — only deletion does.

Troubleshooting

401 Unauthorized. The Authorization header is missing, malformed, or you're sending a Supabase session JWT instead of a server API key. Mint an API key from SettingsAPI Keys and check for stray whitespace before/after the key.

403 Forbidden. The API key's workspace doesn't match the subdomain in the URL. Check both. Don't reuse a key from one workspace against another's URL.

404 Not Found. Slug typo, the source has been deleted, or the source belongs to a different workspace. Verify in the Data Sources modal.

400 Invalid JSON. The body didn't parse. If you're shell-quoting in Bash, watch for nested-quote escaping; use a heredoc or a payload file with --data-binary @file.json.

400 Payload too large. Bodies over 1 MB are rejected. Trim the payload, or split into multiple sources if your shape needs more.

400 Content-Type must be application/json. Set the header explicitly. Some HTTP libraries default to text/plain or application/x-www-form-urlencoded; override.

Graphic field stays blank after a push. The path probably doesn't resolve in the current payload. Open the binding in the picker — the tree shows the live payload, so you can verify the shape matches what you expected.

Field flickers on Lottie graphics. Expected. Lottie graphics apply data updates on next take-on, not live, to avoid mid-animation reloads. Use CG-SDK or OGraf if you need live updates on-air.

429 Too Many Requests. You've exhausted the workspace's per-minute or per-day API quota. The cached payload continues to serve; the next-allowed retry time is in the Retry-After header. Slow your push rate or upgrade your plan.