# 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](authentication).
- Contract: [OpenAPI 3.1](openapi.yaml) · [JSON](openapi.json) · for agents: [llms.txt](https://dev.tickingora.com/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.

```bash
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):

```bash
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:

```json
{
  "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`](reference#rotateToken) 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:

```bash
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/…
```

```cron
# 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](heartbeat).

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

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

### 4. Read the history

```bash
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

```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

```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

```ts
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

- [Monitors & schedules](monitors) — presets, windows, holidays, time zones, PATCH.
- [Heartbeat](heartbeat) — sending signals correctly from cron, PHP, Python, Node, systemd.
- [HTTP Checks](http-checks) — expected status, headers with secrets, retries, recovery checks.
- [For AI coding agents](agents) — rules and a copy-paste prompt.
- [API reference](reference) — every endpoint and field.
