tickingora. API docs OpenAPI llms.txt tickingora.com Get an API key

Tickingora API — overview & quick start

Tickingora watches things that must happen on time: cron jobs, backups, imports, integrations (Heartbeat) and HTTP endpoints (HTTP Check). If the expected signal does not arrive within its time window — or your endpoint does not answer correctly at the scheduled time — you get an e-mail alert, and another one when it recovers.

The Management API lets you (or an AI coding agent) do everything the dashboard wizard does: create monitors, set schedules and alert recipients, read history. It uses the same validation, plan limits and scheduler as the dashboard, so a monitor created through the API looks and behaves exactly like one created by hand.

  • Base URL: https://dev.tickingora.com/api/v1
  • Format: JSON in, JSON out. Times are UTC, ISO 8601 (2026-10-26T05:00:00Z).
  • Auth: Authorization: Bearer <api-key> — see Authentication.
  • Contract: OpenAPI 3.1 · JSON · for agents: llms.txt

Tickingora is currently a test version, provided without any guarantee. Do not rely on it as your only safeguard.

Quick start: monitor a nightly backup in 3 minutes

1. Create an API key

In the dashboard open Settings → API keys, give the key a name, select monitors:write (and monitors:read) and copy the key. It is shown only once.

export TICKINGORA_API_KEY='12|tk_…'

2. Create a Heartbeat monitor

The backup should finish every night between 02:00 and 03:00 (Warsaw time):

curl -sS -X POST https://dev.tickingora.com/api/v1/monitors \
  -H "Authorization: Bearer $TICKINGORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nightly-backup-v1" \
  -d '{
    "type": "heartbeat",
    "external_id": "production:nightly-backup",
    "name": "Nightly database backup",
    "timezone": "Europe/Warsaw",
    "schedule": { "preset": "daily", "days": [1,2,3,4,5,6,7], "start": "02:00", "end": "03:00" }
  }'

The response contains the monitor and, only this once, its secret ping URL:

{
  "data": {
    "id": 42,
    "external_id": "production:nightly-backup",
    "type": "heartbeat",
    "status": "waiting",
    "name": "Nightly database backup",
    "heartbeat": {
      "ping_url": "https://dev.tickingora.com/ping/kN3xQ7…",
      "ping_url_note": "Store this URL now: it is shown only once. Lost it? Call rotate-token."
    },
    "next_runs": [ { "start": "2026-10-27T01:00:00Z", "end": "2026-10-27T02:00:00Z" } ]
  }
}

Store the ping_url as a secret right away. Tickingora keeps only a hash of the token, so the URL is shown once — in this response (or on the dashboard right after creating the monitor). The only exception is a retry of this exact request with the same Idempotency-Key within 24 hours: it gets the same response, ping URL included. Lost it? Generate a new one with rotate-token or Generate new URL in the dashboard; the old one stops working.

external_id is your own stable name for the monitor. Running the same set-up again — tomorrow or in a year — returns 409 MONITOR_ALREADY_EXISTS with the existing monitor's id instead of creating a duplicate. Idempotency-Key additionally makes a single request safe to retry (for 24 hours).

3. Ping after the job succeeds

Cron does not see the variables of your interactive shell, so keep the URL in a file readable only by the job's user:

sudo install -d -o root -g backup -m 750 /etc/tickingora
sudo install -o backup -g backup -m 600 /dev/null /etc/tickingora/backup.env
sudoedit /etc/tickingora/backup.env      # TICKINGORA_PING_URL=https://…/ping/…
# crontab of the "backup" user: the ping runs only if the backup exited with status 0
30 2 * * * . /etc/tickingora/backup.env && /usr/local/bin/backup.sh && { curl -fsS -m 10 --retry 3 "$TICKINGORA_PING_URL" > /dev/null || echo "Tickingora ping failed" >&2; }

A failed ping is reported but never changes the backup's own result.

More patterns (wrapper script, systemd, PHP, Python, Node) in Heartbeat.

Check the connection without affecting the schedule — a test signal is recorded but never counts for a window:

curl -fsS "$TICKINGORA_PING_URL?test=1"
# OK (test signal, not counted)

4. Read the history

curl -sS https://dev.tickingora.com/api/v1/monitors/42/runs -H "Authorization: Bearer $TICKINGORA_API_KEY"

If no signal arrives between 02:00 and 03:00, that window becomes missed, an incident opens and you get one alert e-mail. The next successful window closes the incident and sends a recovery e-mail.

The same in other languages

PHP

$response = (new GuzzleHttp\Client())->post('https://dev.tickingora.com/api/v1/monitors', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('TICKINGORA_API_KEY'),
        'Idempotency-Key' => 'nightly-backup-v1',
    ],
    'json' => [
        'type' => 'heartbeat',
        'external_id' => 'production:nightly-backup',
        'name' => 'Nightly database backup',
        'timezone' => 'Europe/Warsaw',
        'schedule' => ['preset' => 'daily', 'days' => [1, 2, 3, 4, 5, 6, 7], 'start' => '02:00', 'end' => '03:00'],
    ],
]);
$pingUrl = json_decode((string) $response->getBody(), true)['data']['heartbeat']['ping_url'];

Python

import os, requests

r = requests.post(
    "https://dev.tickingora.com/api/v1/monitors",
    headers={"Authorization": f"Bearer {os.environ['TICKINGORA_API_KEY']}", "Idempotency-Key": "nightly-backup-v1"},
    json={
        "type": "heartbeat",
        "external_id": "production:nightly-backup",
        "name": "Nightly database backup",
        "timezone": "Europe/Warsaw",
        "schedule": {"preset": "daily", "days": [1, 2, 3, 4, 5, 6, 7], "start": "02:00", "end": "03:00"},
    },
    timeout=15,
)
r.raise_for_status()
ping_url = r.json()["data"]["heartbeat"]["ping_url"]

JavaScript / TypeScript

const res = await fetch("https://dev.tickingora.com/api/v1/monitors", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TICKINGORA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "nightly-backup-v1",
  },
  body: JSON.stringify({
    type: "heartbeat",
    external_id: "production:nightly-backup",
    name: "Nightly database backup",
    timezone: "Europe/Warsaw",
    schedule: { preset: "daily", days: [1, 2, 3, 4, 5, 6, 7], start: "02:00", end: "03:00" },
  }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data } = await res.json();
const pingUrl: string = data.heartbeat.ping_url;

Where next