Product record
Actual surfaces, not borrowed credibility
Inspect the dashboard, monitor detail, incident record, demo workspace, and deliberate failure drill before trusting the product.
First workflow
Start with one customer-visible workflow. Define the promise, send one healthy signal, run one deliberate failure drill, and confirm Luota opens an incident with enough context for the next operator action.
Proof path
Buyer record
Luota is pre-customer, so the commercial site shows product behavior, public controls, and buying mechanics directly. No invented testimonials, logo walls, or compliance claims.
Product record
Inspect the dashboard, monitor detail, incident record, demo workspace, and deliberate failure drill before trusting the product.
Public controls
Review the current security posture, privacy terms, DPA, subprocessor list, disclosure path, and live service status.
Billing state
Confirm workflow limits, retention, Stripe responsibility, cancellation behavior, and what changes after checkout.
Assembly path
Luota should never make you translate between a marketing claim, an integration guide, and a dashboard table. The same workflow moves from draft to healthy signal to incident to retained history.
Promise
Choose the customer-visible promise: billing access changed, report delivered, generated output appeared, or data stayed fresh.
Signal
Use a heartbeat or run lifecycle event from the job that already runs. Keep the token and monitor id paired.
Healthy proof
A 202 response means the event landed. The monitor detail page shows the latest signal, payload keys, host, and deploy SHA when attached.
Bad-day proof
Skip a heartbeat, fail a run, or let freshness expire. Trust starts only after the incident path is visible.
Handoff
Add owner, runbook, and alert route so the packet is useful when someone else opens it later.
Mode
Best when one success signal is enough — the job runs, the heartbeat lands, the workflow is healthy.
Mode
Best when the job should emit start and finish events and failure context matters: duration, exit reason, output.
Mode
Best when the output should keep changing, even without a visible job boundary — reports, caches, derived data.
Integration examples
Heartbeats prove a job finished. Run lifecycle events prove start, success, failure, duration, and failure context.
Heartbeat example
Use a heartbeat monitor when the job only needs to say “I finished at all.”
await fetch(`${process.env.LUOTA_API_BASE_URL}/v1/monitors/${process.env.LUOTA_MONITOR_ID}/ping`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-luota-key": process.env.LUOTA_INGEST_TOKEN!
},
body: JSON.stringify({
occurredAt: new Date().toISOString(),
payload: { source: "vercel-cron" }
})
});Run lifecycle example
Use a run lifecycle monitor when failure, lateness, stuck runs, or duration should become part of the incident.
- name: Tell Luota the job started
run: |
curl -X POST "$LUOTA_API_BASE_URL/v1/runs/start" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d "{\"monitorId\":\"$LUOTA_MONITOR_ID\",\"externalRunId\":\"${{ github.run_id }}\",\"deploySha\":\"${{ github.sha }}\",\"host\":\"github-actions\"}"Workflow templates
These are not new product silos. They are the first three patterns to wire when “the job ran” is not enough: billing access changed, report delivered, or generated output appeared.
Template
A billing event is complete only after subscription state, entitlement state, and customer notification all match the expected outcome.
// startRun is your small fetch wrapper around /v1/runs/start.
const run = await startRun("billing-entitlement-sync", {
externalRunId: stripeEvent.id,
payload: {
source: "stripe",
eventType: stripeEvent.type,
customerId,
subscriptionId
}
});
await syncSubscriptionState(stripeEvent);
await assertEntitlementState(customerId);
await sendBillingStateEmail(customerId);
await run.success({
summary: "Subscription, entitlement, and notification completed"
});Template
A report is complete only after generation and the provider delivery receipt are both recorded.
// startRun is your small fetch wrapper around /v1/runs/start.
const run = await startRun("weekly-customer-report", {
externalRunId: reportRunId,
payload: { reportId, accountId, periodStart, periodEnd }
});
const report = await renderReport(accountId);
const delivery = await sendReportEmail(report);
if (!delivery.accepted) {
await run.fail({ summary: "Email provider did not accept report delivery", payload: delivery });
return;
}
await run.success({ summary: "Report rendered and accepted by email provider", payload: delivery });Template
A model batch is complete only after generated output validates, persists, and becomes visible to the user.
// startRun is your small fetch wrapper around /v1/runs/start.
const run = await startRun("ai-output-batch", {
externalRunId: batch.id,
payload: { model, promptVersion, inputCount: batch.items.length }
});
const outputs = await runModelBatch(batch);
const validation = await validateOutputs(outputs);
const saved = await persistValidatedOutputs(validation);
await assertUserVisibleOutput(saved.outputId);
await run.success({
summary: "Generated output validated, saved, and visible",
payload: { outputId: saved.outputId, validCount: validation.validCount }
});Failure drill
A workflow monitor has not earned trust until you have watched it fail on purpose. Run one drill before wiring twenty background jobs.
Create a heartbeat workflow with a short grace window, send one healthy ping, then skip the next expected ping. Confirm Luota opens a missed-work incident instead of silently staying green.
Create a run lifecycle workflow, send /runs/start, then deliberately close it with /fail and a realistic summary. Confirm the incident contains the summary, payload, owner, and run timeline.
Create a freshness workflow for a report/table/output timestamp, send one current signal, then let the freshness window expire. Confirm Luota reports stale output, not infrastructure downtime.
Monitor detail
Signed snippet, recent runs, recent incidents, alert channels, and the monitor definition all live on one page — so docs become an operator workflow, not a separate site.
API reference
Every integration uses one of these endpoints with the x-luota-key header. Monitor tokens authorise a single monitor; workspace tokens are accepted only by the workspace-scoped ping route.
All endpoints accept JSON bodies, return JSON, and respond with HTTP 202 Accepted on success.
/v1/monitors/:monitorId/pingHeartbeat pingUsed by heartbeat and freshness monitors. Each ping resets the lateness window; if no ping arrives before the grace expires, an incident opens.
Body fields
occurredAtstring (ISO 8601)OptionalpayloadobjectOptional{
"ok": true,
"monitor": {
"id": "22222222-2222-4222-8222-222222222222",
"status": "healthy"
},
"heartbeat": {
"id": "33333333-3333-4333-8333-333333333333",
"occurredAt": "2026-04-29T01:33:11.398Z"
}
}{ "error": "Invalid ingest key" }X-Request-Id: req_01HX_sample{ "error": "Monitor not found" }{ "code": "request_failed", "error": "Request failed" }{
"error": "Rate limit exceeded",
"policy": "ingest.monitor_token.minute",
"retryAfterSeconds": 21,
"limit": 60
}Retry-After: 21
X-Request-Id: req_01HX_sample/v1/runs/startRun startOpens a run record on a run-lifecycle monitor. Use the returned run.id as the handle for the matching success/fail call below.
Body fields
monitorIduuidRequiredexternalRunIdstringRequiredstartedAtstring (ISO 8601)OptionalpayloadobjectOptionaldeployShastringOptionalhoststringOptionalenvironmentstringOptionaltagsobject<string,string>Optional{
"monitor": {
"id": "22222222-2222-4222-8222-222222222222"
},
"run": {
"id": "44444444-4444-4444-8444-444444444444",
"externalRunId": "nightly-backup-2026-04-29",
"startedAt": "2026-04-29T01:33:11.398Z"
}
}{ "error": "Invalid ingest key" }X-Request-Id: req_01HX_sample{ "error": "Monitor not found" }{
"error": "Rate limit exceeded",
"retryAfterSeconds": 21
}Retry-After: 21
X-Request-Id: req_01HX_sample/v1/runs/:runId/successRun successCloses a run as successful. Any open incident on the same monitor is auto-resolved by the next healthy event.
Body fields
finishedAtstring (ISO 8601)Optionalsummarystring (≤ 240 chars)OptionaloutputstringOptionalpayloadobjectOptionaldeployShastringOptionalhost / environment / tagsstring / string / objectOptional{
"run": {
"id": "44444444-4444-4444-8444-444444444444",
"status": "success",
"finishedAt": "2026-04-29T01:34:02.781Z",
"durationSeconds": 51
}
}{ "error": "Invalid ingest key or run not found" }{ "error": "Run is already failed and cannot be marked successful." }/v1/runs/:runId/failRun failureCloses a run as failed. Opens or updates an incident on the monitor with the failure context.
Body fields
summarystring (1-240 chars)RequiredfinishedAtstring (ISO 8601)OptionalexitCodeintegerOptionaloutputstringOptionalpayloadobjectOptionaldeployShastringOptionalhost / environment / tagsstring / string / objectOptional{
"run": {
"id": "44444444-4444-4444-8444-444444444444",
"status": "failed",
"finishedAt": "2026-04-29T01:34:02.781Z"
},
"monitor": {
"status": "incident"
}
}{ "error": "Invalid ingest key or run not found" }{ "error": "Run is already successful and cannot be marked failed." }Authentication
Each monitor has its own short token derived from a server-side signing secret plus the monitor, workspace, and current token salt. The token only authorises calls to that one monitor. Lose a token, you lose access to exactly one monitor.
Header
x-luota-key: <monitor token>Sent on every ingest request. Any other authorisation header is ignored. Tokens are not JWTs — there is no client-side expiry; the server treats them as opaque.
Scope
One token = one monitor. The token derivation also encodes the workspace id, so a monitor cannot be moved between workspaces by lifting its token.
Rotation
Rotate from the monitor's Integration tab if you suspect a token has leaked. Rotation changes the monitor salt and immediately rejects the previous token on heartbeat and run lifecycle endpoints.
Environment
Set these once in your CI / hosting provider and the snippets above run unmodified.
LUOTA_API_BASE_URLLUOTA_INGEST_TOKENLUOTA_MONITOR_IDFailure modes
Retries, idempotency, payload limits, and clock-skew behaviour — the boring details a real integration needs.
Troubleshooting
Symptom → cause → next step. Designed to short-circuit the diagnostic chain that operators usually have to assemble from logs.
More platforms
Same env-var contract as the Vercel Cron and GitHub Actions examples in the hero. Copy a block, set the three env vars, ship the job.
nightly_sync:
script:
- |
RUN=$(curl -fsS -X POST "$LUOTA_API_BASE_URL/v1/runs/start" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d "{\"monitorId\":\"$LUOTA_MONITOR_ID\",\"externalRunId\":\"$CI_JOB_ID\",\"deploySha\":\"$CI_COMMIT_SHA\"}")
RUN_ID=$(echo "$RUN" | jq -r .run.id)
./run-the-actual-job.sh && \
curl -fsS -X POST "$LUOTA_API_BASE_URL/v1/runs/$RUN_ID/success" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" || \
curl -fsS -X POST "$LUOTA_API_BASE_URL/v1/runs/$RUN_ID/fail" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d '{"summary":"job failed"}'jobs:
hourly_report:
docker: [{ image: cimg/base:current }]
steps:
- run: ./generate-report.sh
- run: |
curl -fsS -X POST "$LUOTA_API_BASE_URL/v1/monitors/$LUOTA_MONITOR_ID/ping" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d "{\"payload\":{\"build\":\"$CIRCLE_BUILD_NUM\",\"sha\":\"$CIRCLE_SHA1\"}}"# Render Cron Job → Start Command:
sh -c 'node ./generate-report.js && curl -fsS -X POST \
"$LUOTA_API_BASE_URL/v1/monitors/$LUOTA_MONITOR_ID/ping" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d "{\"payload\":{\"source\":\"render-cron\"}}"'# fly.toml (scheduled machine entry)
[processes]
refresh_cache = "sh -c 'node refresh.js && curl -fsS -X POST \\
\"$LUOTA_API_BASE_URL/v1/monitors/$LUOTA_MONITOR_ID/ping\" \\
-H \"x-luota-key: $LUOTA_INGEST_TOKEN\"'"
[[scheduled]]
process = "refresh_cache"
schedule = "0 * * * *"# /etc/cron.d/nightly-sync
0 2 * * * deploy /opt/jobs/nightly-sync.sh && \
curl -fsS -X POST \
"$LUOTA_API_BASE_URL/v1/monitors/$LUOTA_MONITOR_ID/ping" \
-H "x-luota-key: $LUOTA_INGEST_TOKEN" \
-H "content-type: application/json" \
-d '{"payload":{"source":"systemd-host"}}'# /etc/systemd/system/nightly-sync.service
[Service]
EnvironmentFile=/etc/luota.env
ExecStart=/opt/jobs/nightly-sync.sh
ExecStartPost=/usr/bin/curl -fsS -X POST \
$LUOTA_API_BASE_URL/v1/monitors/$LUOTA_MONITOR_ID/ping \
-H "x-luota-key: $LUOTA_INGEST_TOKEN"
# /etc/systemd/system/nightly-sync.timer
[Timer]
OnCalendar=*-*-* 02:00:00