Documentation

REST API

Create monitors in the same deploy that ships the jobs.

A monitor created by hand drifts away from the job it watches. Six months later half the monitors guard jobs that no longer exist, and half the new jobs are not covered at all. There is one cure: describe the monitor next to the job and create it in the same deploy.

Authentication

Create a key in the workspace settings. It is shown once — we store only its hash. Pass it in the Authorization header.

bash
curl -s https://tickwatch.dev/api/v1/monitors \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

A key has two scopes: read and write. Read-only is the default — a key sitting in CI that can delete monitors should be a deliberate choice, not a default.

Limits

The rate limit is per workspace and depends on the plan: from 60 requests per minute on Free to 1200 on Business. Current state comes back in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset — read those instead of guessing at sleep intervals.

Errors

json
{
  "error": {
    "code": "invalid_request",
    "message": "Некорректное cron-выражение",
    "field": "cron_expr"
  }
}
  • 401 unauthorized — no key, or it is revoked or expired
  • 403 forbidden — the key lacks the write scope
  • 404 not_found — the object does not exist, or belongs to another workspace
  • 402 limit_reached — the plan limit is exhausted
  • 422 invalid_request — the request body failed validation
  • 429 rate_limited — too many requests

Create a monitor

bash
curl -s -X POST https://tickwatch.dev/api/v1/monitors \
  -H "Authorization: Bearer $TICKWATCH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "cron",
    "cron_expr": "0 3 * * *",
    "name": "Ночной бэкап",
    "tz": "Europe/Moscow",
    "grace_sec": 600,
    "max_duration_sec": 3600
  }'

The response contains the created monitor including ping_url. That is the point of the call: the address is needed right away to put into the job itself, not fetched by a second request.

json
{
  "id": "9d0f…",
  "name": "Ночной бэкап",
  "state": "new",
  "schedule": {
    "kind": "cron",
    "cron_expr": "0 3 * * *",
    "interval_sec": null,
    "tz": "Europe/Moscow",
    "grace_sec": 600,
    "max_duration_sec": 3600
  },
  "ping_url": "https://ping.tickwatch.dev/4eca85c1-…",
  "last_ping_at": null,
  "created_at": "2026-08-23T01:38:28.918Z"
}

Listing and pagination

bash
curl -s "https://tickwatch.dev/api/v1/monitors?limit=50" \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

# Следующая страница — по курсору из next_cursor.
curl -s "https://tickwatch.dev/api/v1/monitors?limit=50&cursor=MjAyNi0wOC0…" \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

Pagination is cursor-based, not offset-based. The list changes underneath the reader, and with offset a client paging through skips records and sees duplicates.

Update and delete

bash
# Поставить на паузу на время планового переезда.
curl -s -X PATCH https://tickwatch.dev/api/v1/monitors/$ID \
  -H "Authorization: Bearer $TICKWATCH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"paused": true}'

# Сменить расписание.
curl -s -X PATCH https://tickwatch.dev/api/v1/monitors/$ID \
  -H "Authorization: Bearer $TICKWATCH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind": "interval", "interval_sec": 900}'

curl -s -X DELETE https://tickwatch.dev/api/v1/monitors/$ID \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

PATCH changes only the fields you send, but the schedule is validated as a whole: missing parts are taken from the monitor’s current state. Unknown fields are rejected with 422 — a silently swallowed typo in a field name would mean a monitor with a schedule its author never intended.

Runs and incidents

bash
# Последние запуски с кодами возврата и хвостом вывода.
curl -s "https://tickwatch.dev/api/v1/monitors/$ID/runs?limit=20" \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

# Что сломано прямо сейчас.
curl -s "https://tickwatch.dev/api/v1/incidents?status=open" \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

# Отметить, что дежурный уже разбирается.
curl -s -X POST https://tickwatch.dev/api/v1/incidents/$INCIDENT/ack \
  -H "Authorization: Bearer $TICKWATCH_TOKEN"

Acknowledging does not resolve the incident — only a successful ping from the job itself does. It exists so nobody else piles onto the same outage, and repeating the call does not move the original acknowledgement time.

Example: provisioning with the deploy

bash
#!/usr/bin/env bash
set -euo pipefail

API="https://tickwatch.dev/api/v1"
AUTH="Authorization: Bearer $TICKWATCH_TOKEN"
NAME="nightly-report"

# Ищем монитор по имени среди существующих.
existing=$(curl -fsS "$API/monitors?limit=200" -H "$AUTH" \
  | jq -r --arg n "$NAME" '.data[] | select(.name == $n) | .id')

if [ -z "$existing" ]; then
  created=$(curl -fsS -X POST "$API/monitors" -H "$AUTH" \
    -H 'Content-Type: application/json' \
    -d "{\"kind\":\"cron\",\"cron_expr\":\"0 3 * * *\",\"name\":\"$NAME\"}")
  ping=$(echo "$created" | jq -r '.ping_url')
else
  ping=$(curl -fsS "$API/monitors/$existing" -H "$AUTH" | jq -r '.ping_url')
fi

# Подставляем адрес в саму задачу — тем же деплоем.
kubectl create secret generic tickwatch \
  --from-literal=ping-url="$ping" \
  --dry-run=client -o yaml | kubectl apply -f -
REST API · Tickwatch