Documentation
Quickstart
Get your first cron job running in under five minutes.
1. Install the SDK
go get github.com/tickstem/cron
2. Get an API key
Sign in at app.tickstem.dev, go to API Keys, and create a key. Store it as an environment variable:
export TICKSTEM_API_KEY=tsk_live_...
3. Register your first job
package main
import (
"context"
"log"
"os"
"github.com/tickstem/cron"
)
func main() {
client := cron.New(os.Getenv("TICKSTEM_API_KEY"))
job, err := client.Register(context.Background(), cron.RegisterParams{
Name: "daily-cleanup",
Schedule: "0 2 * * *", // every day at 02:00 UTC
Endpoint: "https://yourapp.com/jobs/cleanup",
})
if err != nil {
log.Fatal(err)
}
log.Printf("registered: %s next run: %s", job.ID, job.NextRunAt)
}
Tickstem will call POST https://yourapp.com/jobs/cleanup every day at 02:00 UTC. Your endpoint just needs to return a 2xx status — no SDK required on the receiving side.
Core concepts
Jobs
A Job is a named schedule that calls an HTTP endpoint. Jobs are identified by a stable id (e.g. job_abc123). You can register the same job on every deploy — if a job with the same name already exists for your account, Register upserts it.
Executions
Each time a job fires, an Execution is created. It records the outcome (success, failed, timeout), HTTP status code, duration, and any error message. History is retained for 7 days on the Free plan, 30 days on Starter, and 90 days on Pro and Business.
Schedules
Schedules use standard 5-field cron syntax in UTC. See the cron reference for examples.
Installation
go get github.com/tickstem/cron
Requires Go 1.21 or later. The SDK has no third-party dependencies.
Client
// Minimal — points at https://api.tickstem.dev/v1
client := cron.New(os.Getenv("TICKSTEM_API_KEY"))
// With options
client := cron.New(apiKey,
cron.WithBaseURL("http://localhost:8090/v1"), // local dev
)
The client is safe for concurrent use and should be created once and reused.
Register a job
job, err := client.Register(ctx, cron.RegisterParams{
Name: "send-digest",
Description: "Weekly email digest to all users",
Schedule: "0 9 * * 1", // every Monday at 09:00 UTC
Endpoint: "https://yourapp.com/jobs/digest",
Method: "POST", // default
Headers: map[string]string{
"X-Tickstem-Secret": os.Getenv("CRON_SECRET"), // see Securing your endpoint
},
TimeoutSecs: 60, // default: 30
})
Parameters
| Field | Type | Description |
|---|---|---|
| Name* | string | Human-readable label. Used as the upsert key — registering the same name updates the existing job. |
| Schedule* | string | 5-field cron expression in UTC. |
| Endpoint* | string | HTTPS URL to call on each execution. |
| Description | string | Optional note shown in the dashboard. |
| Method | string | HTTP method. Defaults to POST. |
| Headers | map[string]string | Extra headers sent with each request. Use for shared secrets. |
| TimeoutSecs | int | Seconds before the execution is marked timeout. Defaults to 30. |
List & Get
// list all jobs
jobs, err := client.List(ctx)
for _, j := range jobs {
fmt.Printf("%s %s next: %s\n", j.ID, j.Schedule, j.NextRunAt)
}
// get a single job
job, err := client.Get(ctx, "job_abc123")
Pause & Resume
job, err := client.Pause(ctx, jobID)
job, err := client.Resume(ctx, jobID)
A paused job is not executed but retains its schedule. Resume it to re-enable execution from the next scheduled time.
Delete
err := client.Delete(ctx, jobID)
Permanently removes the job and all its execution history.
Execution history
executions, err := client.Executions(ctx, jobID)
for _, e := range executions {
fmt.Printf("%s status=%s duration=%dms\n",
e.ScheduledAt.Format(time.RFC3339),
e.Status,
*e.DurationMs,
)
}
Returns executions in reverse chronological order. Each Execution includes:
Status—success,failed,timeout,running, orpendingStatusCode— HTTP response code from your endpointDurationMs— wall-clock time of the HTTP callError— error message when status isfailedortimeoutScheduledAt,StartedAt,FinishedAt— timing breakdown
Error handling
job, err := client.Get(ctx, someID)
if err != nil {
if cron.IsNotFound(err) {
// job doesn't exist
}
if cron.IsUnauthorized(err) {
// invalid or revoked API key
}
if cron.IsQuotaExceeded(err) {
// monthly execution limit reached
// upgrade at app.tickstem.dev/dashboard/billing
}
// inspect the raw API error
var apiErr *cron.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.StatusCode, apiErr.Message)
}
}
Securing your endpoint
Tickstem calls your endpoint over the public internet. Any caller that knows your URL can trigger it. Add a shared secret to reject unauthorized requests.
1. Generate a secret
openssl rand -hex 32
Store the output as CRON_SECRET in both your app's environment and as a job header in Tickstem.
2. Register the job with the secret
job, err := client.Register(ctx, cron.RegisterParams{
Name: "daily-cleanup",
Schedule: "0 2 * * *",
Endpoint: "https://yourapp.com/jobs/cleanup",
Headers: map[string]string{
"X-Tickstem-Secret": os.Getenv("CRON_SECRET"),
},
})
3. Validate in your handler
func cronHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Tickstem-Secret") != os.Getenv("CRON_SECRET") {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// ... do work
}
Local dev with tsk-local
tsk-local runs the full Tickstem API contract in-memory on your machine so you can test job handlers without touching the live API or needing real credentials.
Install
go install github.com/tickstem/cron/cmd/tsk-local@latest
Run
tsk-local # starts on :8090
tsk-local --port 9000 # custom port
Point the SDK at it
client := cron.New("any-key",
cron.WithBaseURL("http://localhost:8090/v1"))
The local dashboard at http://localhost:8090 shows registered jobs, execution history, and a Run button for instant manual triggers.
Cron expression reference
Schedules use standard 5-field cron syntax. All times are UTC.
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–6, Sun=0)
│ │ │ │ │
* * * * *
| Expression | Meaning |
|---|---|
| * * * * * | Every minute |
| */15 * * * * | Every 15 minutes |
| 0 * * * * | Every hour at :00 |
| 0 2 * * * | Daily at 02:00 UTC |
| 0 9 * * 1 | Every Monday at 09:00 UTC |
| 0 0 1 * * | First day of every month |
| 0 0 * * 0 | Every Sunday at midnight |
Use crontab.guru to build and validate expressions.
Email Verification
The Tickstem email verification API checks whether an email address is deliverable before you store it. It runs four checks in sequence:
- Syntax — RFC 5322 parse. Catches
user@@example, missing TLD, etc. - MX lookup — confirms the domain has mail exchange records. Rejects
[email protected]without a network call to the mail server. - Disposable detection — flags addresses on known throwaway services (Mailinator, Guerrilla Mail, and 200+ others).
- Role-based detection — flags generic inboxes (
admin@,info@,noreply@) that are unlikely to belong to a real person.
The API returns a structured result for every check. You decide what to do with it — block, warn, or accept.
Installation
go get github.com/tickstem/verify
Requires Go 1.22 or later. No third-party runtime dependencies.
Same API key as the cron SDK — no additional credentials needed.
Verify an email
import "github.com/tickstem/verify"
client := verify.New(os.Getenv("TICKSTEM_API_KEY"))
result, err := client.Verify(ctx, "[email protected]")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Valid) // true if safe to store
fmt.Println(result.Disposable) // true if throwaway address
fmt.Println(result.RoleBased) // true if generic inbox
fmt.Println(result.MXFound) // true if domain accepts mail
fmt.Println(result.Reason) // human-readable explanation when invalid
Result fields
| Field | Type | Description |
|---|---|---|
| Valid | bool | True only when syntax is correct, MX records exist, and the domain is not disposable. |
| MXFound | bool | True if the domain has at least one MX record. |
| Disposable | bool | True if the domain is a known throwaway service. |
| RoleBased | bool | True if the local part matches a generic inbox prefix. Does not affect Valid. |
| Reason | string | Human-readable explanation when Valid is false. |
Common pattern: block disposable, warn on role-based
result, err := client.Verify(ctx, email)
if err != nil {
// network or quota error — fail open or closed depending on your policy
return err
}
if !result.Valid {
return fmt.Errorf("email address not accepted: %s", result.Reason)
}
if result.RoleBased {
// warn but don't block — some teams share an inbox
log.Printf("role-based address submitted: %s", email)
}
History
Every verification is stored in your account history. Retrieve it programmatically to audit, export, or build analytics on top of.
page, err := client.ListHistory(ctx, verify.ListHistoryParams{
Limit: 50,
Offset: 0,
})
if err != nil {
log.Fatal(err)
}
for _, v := range page.Verifications {
fmt.Printf("%s valid=%v disposable=%v checked=%s\n",
v.Email, v.Valid, v.Disposable, v.CreatedAt.Format(time.RFC3339))
}
Error handling
result, err := client.Verify(ctx, email)
if err != nil {
if verify.IsUnauthorized(err) {
// invalid or revoked API key
}
if verify.IsQuotaExceeded(err) {
// monthly verification limit reached
// upgrade at app.tickstem.dev/dashboard/billing
}
var apiErr *verify.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.StatusCode, apiErr.Message)
}
}
Uptime Monitoring
Tickstem polls your HTTP endpoints on a configurable interval and alerts you by email when they go down or recover. A monitor is a URL + an interval — Tickstem handles the scheduling, check storage, and notifications.
- Each check issues a
GETrequest with your configured timeout.2xxand3xxresponses are recorded as up. 4xx,5xx, connection timeouts, and DNS failures are recorded as down.- After two consecutive failures the monitor flips to
failingstatus and sends an email alert. - When the endpoint recovers, you get a recovery notification and the monitor returns to
active.
Create a monitor
From the dashboard: Monitors → New monitor. Or via the REST API:
curl -X POST https://api.tickstem.dev/v1/monitors \
-H "Authorization: Bearer $TICKSTEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production API",
"url": "https://api.yourapp.com/health",
"interval_secs": 60,
"timeout_secs": 10
}'
Parameters
| Field | Type | Description |
|---|---|---|
| name* | string | Human-readable label shown in the dashboard. |
| url* | string | HTTPS URL to poll. |
| interval_secs | int | Check frequency in seconds. Plan minimum: Free 300, Starter 60, Pro/Business 30. Default: 60. |
| timeout_secs | int | Seconds before marking the check as timeout. Range: 5–30. Default: 10. |
Monitor status values
| Status | Meaning |
|---|---|
| active | Checks are running normally. |
| failing | Two or more consecutive failures. Email alert has been sent. |
| paused | Checks are suspended. No alerts will fire. |
Pause, resume, delete
# pause
curl -X PATCH https://api.tickstem.dev/v1/monitors/{id}/pause \
-H "Authorization: Bearer $TICKSTEM_API_KEY"
# resume
curl -X PATCH https://api.tickstem.dev/v1/monitors/{id}/resume \
-H "Authorization: Bearer $TICKSTEM_API_KEY"
# delete (removes the monitor and all its check history)
curl -X DELETE https://api.tickstem.dev/v1/monitors/{id} \
-H "Authorization: Bearer $TICKSTEM_API_KEY"
Check history
curl https://api.tickstem.dev/v1/monitors/{id}/checks \
-H "Authorization: Bearer $TICKSTEM_API_KEY"
Returns checks in reverse chronological order (most recent first). Each check includes:
status—up,down, ortimeoutstatus_code— HTTP response code from your endpoint, if a response was receivedduration_ms— wall-clock time of the HTTP requesterror— error message fordownandtimeoutcheckschecked_at— RFC 3339 timestamp of when the check ran
Check history is retained for 7 days on Free, 30 days on Starter, and 90 days on Pro and Business — the same retention policy as cron execution logs.
Error handling
The REST API returns standard error shapes. The Node.js SDK maps them to APIError — see Node.js — Uptime monitoring for SDK-level examples.
| Status | Meaning |
|---|---|
| 400 | Invalid parameters — interval_secs or timeout_secs out of bounds. |
| 401 | Missing or invalid API key. |
| 402 | Monitor quota reached for your plan, or interval_secs is below your plan's minimum. |
| 404 | Monitor not found, or it belongs to a different account. |
Plan limits
| Plan | Monitors | Min interval | Check history |
|---|---|---|---|
| Free | 5 | 5 min (300s) | 7 days |
| Starter | 20 | 1 min (60s) | 30 days |
| Pro | 100 | 30s | 90 days |
| Business | Unlimited | 30s | 90 days |
The monitor count is a hard ceiling — creating a monitor when you're at the limit returns a 402. Upgrade at app.tickstem.dev/dashboard/billing.
Heartbeat Monitoring
A heartbeat is a dead-man's switch: your job POSTs to a ping URL after each successful run. If the ping stops arriving within the expected interval + grace window, Tickstem sends you an email alert.
- Each heartbeat gets a unique 64-character hex token. The token itself is the credential — no API key needed to ping.
- After two consecutive missed intervals the heartbeat flips to
failingstatus and an alert is sent. - The grace window is an extra buffer after the deadline before any alert fires — useful for jobs with variable runtime.
- Pause a heartbeat during planned maintenance so missed pings don't trigger alerts.
Create a heartbeat
From the dashboard: Heartbeats → New heartbeat. Or via the REST API:
curl -X POST https://api.tickstem.dev/v1/heartbeats \
-H "Authorization: Bearer $TICKSTEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "daily backup",
"interval_secs": 86400,
"grace_secs": 3600
}'
The response includes a token field — this is the unique credential used to ping this heartbeat. Copy it and store it alongside your job:
{
"id": "hb_01abc...",
"name": "daily backup",
"token": "a3f8c2d1e4b5f6...",
"interval_secs": 86400,
"grace_secs": 3600,
"status": "active"
}
Parameters
| Field | Type | Description |
|---|---|---|
| name* | string | Human-readable label shown in the dashboard. |
| interval_secs | int | Expected time between pings in seconds. Range: 60–86400. Default: 3600 (1 hour). |
| grace_secs | int | Buffer after the deadline before alerting. Range: 0–86400. Default: 300 (5 minutes). |
Heartbeat status values
| Status | Meaning |
|---|---|
| active | Receiving pings within the expected window. |
| failing | Two or more consecutive intervals missed. Email alert has been sent. |
| paused | Alert suppression active. Missed pings are ignored. |
Pause, resume, delete
# pause
curl -X PATCH https://api.tickstem.dev/v1/heartbeats/{id} \
-H "Authorization: Bearer $TICKSTEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"paused"}'
# resume
curl -X PATCH https://api.tickstem.dev/v1/heartbeats/{id} \
-H "Authorization: Bearer $TICKSTEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"active"}'
# delete (removes the heartbeat and all ping history)
curl -X DELETE https://api.tickstem.dev/v1/heartbeats/{id} \
-H "Authorization: Bearer $TICKSTEM_API_KEY"
Pinging
Once you have created a heartbeat and copied its token, call the ping endpoint at the end of every successful job run. No API key is needed — the token in the URL is the credential.
Step 1: Get your token — from the dashboard (Heartbeats → your heartbeat → token field) or from the token field in the create response above.
Step 2: Call the ping URL after every successful run:
# Replace the token below with your actual token from the dashboard
curl -X POST https://api.tickstem.dev/v1/heartbeats/a3f8c2d1e4b5f6.../ping
The response is {"status":"ok"} on success, or {"status":"paused"} if the heartbeat is currently paused.
Ping failures should be non-fatal — log the error but don't let a transient network issue block your job from completing.
import "github.com/tickstem/heartbeat"
client := heartbeat.New(os.Getenv("TICKSTEM_API_KEY"))
// At the end of every successful run:
if err := client.Ping(ctx, token); err != nil {
log.Println("heartbeat ping failed:", err) // non-fatal
}
Plan limits
| Plan | Heartbeats |
|---|---|
| Free | 5 |
| Starter | 20 |
| Pro | 100 |
| Business | Unlimited |
The heartbeat count is a hard ceiling — creating one when you're at the limit returns a 402. Upgrade at app.tickstem.dev/dashboard/billing.
Node.js SDK — Installation
The Node.js SDK is split into focused packages. Install only what you need — the same API key works for all of them.
Cron jobs
npm install @tickstem/cron
# or
pnpm add @tickstem/cron
Email verification
npm install @tickstem/verify
# or
pnpm add @tickstem/verify
Uptime monitoring
npm install @tickstem/uptime
# or
pnpm add @tickstem/uptime
Heartbeat monitoring
npm install @tickstem/heartbeat
# or
pnpm add @tickstem/heartbeat
Requires Node.js 18 or later (uses native fetch). Works with JavaScript and TypeScript. ESM only — add "type": "module" to your package.json, or use a bundler (Next.js, Vite, esbuild).
Node.js — Cron jobs
Create a client
import { CronClient } from "@tickstem/cron"
const cron = new CronClient(process.env.TICKSTEM_API_KEY)
Register a job
const job = await cron.register({
name: "daily-cleanup",
schedule: "0 2 * * *",
endpoint: "https://yourapp.com/jobs/cleanup",
})
console.log(job.id, job.next_run_at)
List, pause, resume, delete
const jobs = await cron.list()
await cron.pause(jobId)
await cron.resume(jobId)
await cron.delete(jobId)
Execution history
const executions = await cron.executions(jobId)
for (const e of executions) {
console.log(e.id, e.status, e.duration_ms)
}
Node.js — Email verification
Create a client
import { VerifyClient } from "@tickstem/verify"
const verify = new VerifyClient(process.env.TICKSTEM_API_KEY)
Verify an email
const result = await verify.verify("[email protected]")
if (!result.valid) {
console.log(result.reason)
}
if (result.disposable) {
return { error: "Disposable email addresses are not allowed." }
}
if (result.role_based) {
return { error: "Please use a personal email address." }
}
History
const history = await verify.history()
for (const entry of history) {
console.log(entry.email, entry.valid, entry.created_at)
}
Node.js — Uptime monitoring
Create a client
import { UptimeClient } from "@tickstem/uptime"
const uptime = new UptimeClient(process.env.TICKSTEM_API_KEY)
Create and manage monitors
// create
const monitor = await uptime.create({
name: "Production API",
url: "https://api.yourapp.com/health",
interval_secs: 60,
timeout_secs: 10,
})
// list all monitors
const monitors = await uptime.list()
// get a single monitor
const m = await uptime.get(monitor.id)
// pause / resume / delete
await uptime.pause(monitor.id)
await uptime.resume(monitor.id)
await uptime.delete(monitor.id)
Check history
const checks = await uptime.checks(monitor.id)
for (const check of checks) {
console.log(check.status, check.duration_ms + "ms", check.checked_at)
}
// check.status: "up" | "down" | "timeout"
// check.status_code: number | null
// check.error: string
Error handling
import { isUnauthorized, isQuotaExceeded, APIError } from "@tickstem/uptime"
try {
await uptime.create({ name: "x", url: "https://x.com" })
} catch (err) {
if (isQuotaExceeded(err)) {
// monitor limit reached, or interval_secs below plan minimum
}
if (err instanceof APIError) {
console.log(err.statusCode, err.message)
}
}
Node.js — Heartbeat monitoring
Create a client
import { HeartbeatClient } from "@tickstem/heartbeat"
const hb = new HeartbeatClient(process.env.TICKSTEM_API_KEY)
Create and manage heartbeats
// create once — save the token somewhere permanent
const heartbeat = await hb.create({
name: "daily backup",
interval_secs: 86400,
grace_secs: 3600,
})
// list all heartbeats
const all = await hb.list()
// get a single heartbeat
const h = await hb.get(heartbeat.id)
// update fields
await hb.update(heartbeat.id, { name: "weekly backup", interval_secs: 604800 })
// pause / resume / delete
await hb.pause(heartbeat.id)
await hb.resume(heartbeat.id)
await hb.delete(heartbeat.id)
Pinging
// at the end of every successful job run — no API key needed
try {
await hb.ping(heartbeat.token)
} catch (err) {
console.error("heartbeat ping failed:", err) // non-fatal — don't block your job
}
Ping history
const pings = await hb.pings(heartbeat.id, { limit: 50 })
for (const ping of pings) {
console.log(ping.pinged_at)
}
Node.js — Error handling
import { isUnauthorized, isQuotaExceeded, APIError } from "@tickstem/cron"
try {
await cron.list()
} catch (err) {
if (isUnauthorized(err)) {
// invalid or revoked API key
}
if (isQuotaExceeded(err)) {
// monthly execution limit reached
// upgrade at app.tickstem.dev/dashboard/billing
}
if (err instanceof APIError) {
console.log(err.statusCode, err.message)
}
}
isUnauthorized and isQuotaExceeded from @tickstem/verify for identical error handling in the email verification client.
Python SDK — Installation
A single package covers all four tools. Install only what you need — the same API key works for everything.
pip install tickstem
Requires Python 3.11 or later. The only runtime dependency is httpx.
Same API key as all other SDKs — no additional credentials needed.
Python SDK — Cron jobs
from tickstem import CronClient, CronRegisterParams
client = CronClient(os.environ["TICKSTEM_API_KEY"])
job = client.register(CronRegisterParams(
name="daily-cleanup",
schedule="0 2 * * *",
endpoint="https://yourapp.com/jobs/cleanup",
method="POST",
timeout_secs=60,
))
print(job.id, job.next_run_at)
List, pause, resume, delete
jobs = client.list()
job = client.get(job_id)
client.pause(job_id)
client.resume(job_id)
client.delete(job_id)
Execution history
executions = client.executions(job_id)
for e in executions:
print(e.status, e.duration_ms, "ms")
Python SDK — Email verification
from tickstem import VerifyClient
client = VerifyClient(os.environ["TICKSTEM_API_KEY"])
result = client.verify("[email protected]")
if not result.valid:
raise ValueError(f"Email not accepted: {result.reason}")
if result.disposable:
raise ValueError("Disposable email addresses are not allowed.")
if result.role_based:
print("Warning: role-based address") # warn but don't block
History
history = client.history(limit=50)
for entry in history:
print(entry.email, entry.valid, entry.created_at)
Python SDK — Uptime monitoring
from tickstem import UptimeClient, UptimeCreateParams, Assertion
client = UptimeClient(os.environ["TICKSTEM_API_KEY"])
monitor = client.create(UptimeCreateParams(
name="Production API",
url="https://api.yourapp.com/health",
interval_secs=60,
assertions=[
Assertion(source="status_code", comparison="eq", target="200"),
Assertion(source="response_time", comparison="lt", target="2000"),
],
))
monitors = client.list()
checks = client.checks(monitor.id, limit=50)
for check in checks:
print(check.status, check.duration_ms, "ms")
client.delete(monitor.id)
Python SDK — Heartbeat monitoring
from tickstem import HeartbeatClient, HeartbeatCreateParams
client = HeartbeatClient(os.environ["TICKSTEM_API_KEY"])
hb = client.create(HeartbeatCreateParams(
name="daily backup",
interval_secs=86400,
grace_secs=3600,
))
# At the end of every successful job run — no API key needed
try:
client.ping(hb.token)
except Exception as e:
print("heartbeat ping failed:", e) # non-fatal
Pause, resume, delete
client.pause(hb.id)
client.resume(hb.id)
client.delete(hb.id)
pings = client.pings(hb.id, limit=50)
Python SDK — Error handling
from tickstem import CronClient, APIError, is_unauthorized, is_quota_exceeded
client = CronClient(os.environ["TICKSTEM_API_KEY"])
try:
jobs = client.list()
except APIError as err:
if is_unauthorized(err):
print("invalid or revoked API key")
elif is_quota_exceeded(err):
print("monthly limit reached — upgrade at app.tickstem.dev/dashboard/billing")
else:
print(f"error {err.status}: {err.message}")
with CronClient(...) as client: to ensure the underlying HTTP connection is closed when you're done.
Status Pages
Every Tickstem account can publish a public status page that auto-generates from its uptime monitors and heartbeat monitors. The page updates itself — no manual incident posts, no maintenance required.
Your status page shows two sections:
- Services — uptime monitors with current status (Operational / Degraded) and 30-day uptime percentage.
- Scheduled Jobs — heartbeat monitors with current status (Running / Missing) and last ping time.
Setup
Go to Dashboard → Status Page and:
- Toggle Enable on.
- Choose a slug — lowercase letters, numbers, and hyphens. Your page will be at
tickstem.dev/status/{slug}. - Optionally set a title (shown as the page heading).
- Set visibility to Private while you set up, then flip to Public when ready to share.
| Visibility | Who can see it |
|---|---|
| Private | Only you (while logged in) — useful for previewing before going live. |
| Public | Anyone with the URL — share with your users. |
What appears on the page
The page is built from your existing monitors and heartbeats — no extra configuration needed.
| Source | Shown as | Metric |
|---|---|---|
| Uptime monitor — active | Operational | 30-day uptime % |
| Uptime monitor — failing | Degraded | 30-day uptime % |
| Uptime monitor — paused | Hidden | — |
| Heartbeat — active, pinged | Running | Last seen timestamp |
| Heartbeat — failing | Missing | Last seen timestamp |
| Heartbeat — paused | Hidden | — |
The overall status banner at the top shows All systems operational when every visible monitor and heartbeat is healthy, and Some systems are experiencing issues when any is failing.
All public status pages include a Powered by Tickstem footer link.