# WatchCron — Complete API Documentation > This file contains the full API reference for WatchCron in plain Markdown, > designed for consumption by AI assistants (ChatGPT, Claude, etc.). > Human-readable docs: https://watchcron.com/docs/api > OpenAPI spec: https://watchcron.com/openapi.json ## Overview WatchCron monitors cron jobs, scheduled tasks, and background workers. When a job fails to ping WatchCron on time, it sends alerts via email, Slack, Telegram, SMS, voice calls, webhooks, PagerDuty, OpsGenie, Discord, and Microsoft Teams. **Base URL:** `https://watchcron.com/api/v1` **Authentication:** Bearer token in `Authorization` header **Content-Type:** `application/json` for POST/PUT requests **Response format:** JSON with `data` wrapper and `meta.pagination` for lists --- ## Authentication Every API request requires an API key sent as a Bearer token: ``` Authorization: Bearer YOUR_API_KEY ``` API keys are project-scoped — each key can only access checks and channels within its project. Generate keys in Dashboard → Project Settings → API Keys. Keys are shown only once on creation. WatchCron stores a SHA-256 hash and an 8-character prefix for identification. --- ## Rate Limits Requests are rate-limited per API key on a 60-second rolling window: | Plan | Requests/minute | |----------|----------------| | Free | 100 | | Starter | 200 | | Pro | 500 | | Business | 1,000 | When exceeded, the API returns HTTP 429 with a `Retry-After` header (seconds). --- ## Pagination List endpoints return paginated results. Query parameters: - `per_page` (integer, default: 25) — results per page - `page` (integer, default: 1) — page number Response includes: ```json { "data": [...], "meta": { "pagination": { "total": 87, "per_page": 25, "current_page": 1, "last_page": 4 } } } ``` --- ## Error Responses | Status | Meaning | When | |--------|------------------|-----------------------------------------| | 200 | OK | Request succeeded | | 201 | Created | Resource created | | 204 | No Content | Resource deleted | | 401 | Unauthorized | Missing or invalid API key | | 404 | Not Found | Resource doesn't exist or wrong project | | 422 | Validation Error | Invalid request body | | 429 | Rate Limited | Too many requests | | 500 | Server Error | Unexpected error | Error response body: ```json {"error": "Error message here"} ``` --- ## Endpoints ### GET /api/v1/checks — List checks Returns all checks in the project. **Query parameters:** - `tag` (string) — filter by tag (exact match) - `per_page` (integer, default: 25) - `page` (integer, default: 1) **Example:** ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/checks?tag=production&per_page=10" ``` **Response 200:** ```json { "data": [ { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Nightly Backup", "description": "Backs up production database", "status": "up", "schedule_type": "cron", "period_seconds": null, "cron_expression": "0 2 * * *", "timezone": "UTC", "grace_seconds": 600, "tags": ["production", "database"], "is_paused": false, "ping_url": "https://watchcron.com/ping/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "last_ping_at": "2026-09-14T02:00:12Z", "next_expected_at": "2026-09-15T02:00:00Z", "created_at": "2026-03-22T10:00:00Z", "updated_at": "2026-09-14T02:00:12Z" } ], "meta": { "pagination": {"total": 42, "per_page": 10, "current_page": 1, "last_page": 5} } } ``` --- ### POST /api/v1/checks — Create a check **Request body:** | Field | Type | Required | Description | |------------------|---------|-------------|--------------------------------------------| | name | string | Yes | Check name (max 255 chars) | | description | string | No | Optional description (max 1000 chars) | | schedule_type | string | Yes | `simple` or `cron` | | period_seconds | integer | If simple | Interval in seconds (min 60) | | cron_expression | string | If cron | 5-field cron expression (max 100 chars) | | timezone | string | No | IANA timezone (default: UTC) | | grace_seconds | integer | No | Grace period in seconds | | tags | array | No | List of tag strings (each max 50 chars) | **Example (cron schedule):** ```bash curl -X POST https://watchcron.com/api/v1/checks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Nightly Backup", "schedule_type": "cron", "cron_expression": "0 2 * * *", "timezone": "America/New_York", "grace_seconds": 600, "tags": ["production", "database"] }' ``` **Example (simple interval):** ```bash curl -X POST https://watchcron.com/api/v1/checks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Health Check", "schedule_type": "simple", "period_seconds": 300, "grace_seconds": 120 }' ``` **Response 201:** Check object (same fields as List response). **Validation errors (422):** ```json {"error": "Invalid cron expression"} ``` --- ### GET /api/v1/checks/{uuid} — Get a check Returns full details for a single check. ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/checks/a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` **Response 200:** Single check object wrapped in `{"data": {...}}`. --- ### PUT /api/v1/checks/{uuid} — Update a check Partial update — only include fields you want to change. | Field | Type | Description | |------------------|--------------|--------------------------------| | name | string | Max 255 chars | | description | string\|null | Max 1000 chars | | schedule_type | string | `simple` or `cron` | | period_seconds | integer\|null| Min 60 | | cron_expression | string\|null | Max 100 chars | | timezone | string\|null | IANA timezone | | grace_seconds | integer\|null| Grace period | | tags | array\|null | Replaces all existing tags | ```bash curl -X PUT https://watchcron.com/api/v1/checks/YOUR-UUID \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Backup v2", "grace_seconds": 900}' ``` **Response 200:** Updated check object. **Note:** Sending `"tags": ["new-tag"]` replaces all existing tags. Send `"tags": null` to clear tags. --- ### DELETE /api/v1/checks/{uuid} — Delete a check Permanently deletes a check and all associated ping data. ```bash curl -X DELETE -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/checks/YOUR-UUID" ``` **Response 204:** No content. --- ### PATCH /api/v1/checks/{uuid}/pause — Pause or unpause | Field | Type | Default | Description | |--------|---------|---------|-----------------------------------| | paused | boolean | true | `true` to pause, `false` to resume| ```bash # Pause curl -X PATCH https://watchcron.com/api/v1/checks/YOUR-UUID/pause \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"paused": true}' # Resume curl -X PATCH https://watchcron.com/api/v1/checks/YOUR-UUID/pause \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"paused": false}' ``` **Response 200:** Updated check object. Status becomes `paused` or `new`. --- ### GET /api/v1/checks/{uuid}/pings — List pings Returns ping history for a check, newest first. **Query parameters:** - `per_page` (integer, default: 25) - `page` (integer, default: 1) ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/checks/YOUR-UUID/pings?per_page=50" ``` **Response 200:** ```json { "data": [ { "type": "success", "remote_ip": "203.0.113.42", "duration_ms": 4523, "created_at": "2026-09-14T02:00:12Z" }, { "type": "start", "remote_ip": "203.0.113.42", "duration_ms": null, "created_at": "2026-09-14T01:59:58Z" } ], "meta": { "pagination": {"total": 1847, "per_page": 50, "current_page": 1, "last_page": 37} } } ``` **Ping types:** - `success` — Job completed successfully - `start` — Job started (enables duration tracking) - `fail` — Explicit failure (triggers immediate alert) - `log` — Log output attached (max 100 KB) --- ### GET /api/v1/checks/{uuid}/flips — List status flips Returns status transition history (e.g. up → grace → down → up). ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/checks/YOUR-UUID/flips" ``` **Response 200:** ```json { "data": [ {"old_status": "up", "new_status": "grace", "created_at": "2026-09-13T14:05:00Z"}, {"old_status": "grace", "new_status": "down", "created_at": "2026-09-13T14:15:00Z"}, {"old_status": "down", "new_status": "up", "created_at": "2026-09-13T14:22:37Z"} ], "meta": { "pagination": {"total": 12, "per_page": 25, "current_page": 1, "last_page": 1} } } ``` **Status values:** `new`, `up`, `grace`, `down`, `paused` --- ### GET /api/v1/channels — List notification channels Returns all notification channels in the project. ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://watchcron.com/api/v1/channels" ``` **Response 200:** ```json { "data": [ { "id": 1, "name": "DevOps Email", "type": "email", "is_active": true, "checks_count": 15, "created_at": "2026-03-10T08:00:00Z" }, { "id": 2, "name": "#alerts Slack", "type": "slack", "is_active": true, "checks_count": 8, "created_at": "2026-04-15T12:30:00Z" } ] } ``` **Channel types:** `email`, `webhook`, `slack`, `telegram`, `discord`, `ms_teams`, `sms`, `voice`, `pagerduty`, `opsgenie` **Plan availability:** - All plans: email, webhook - Starter+: slack, telegram, discord, ms_teams - Pro+: sms, pagerduty, opsgenie - Business: voice --- ## Ping Endpoints (No Auth Required) These endpoints are separate from the REST API. They accept GET and POST requests and require no authentication — just the check UUID (or custom slug) in the URL. | Method | Endpoint | Description | |--------|-----------------------|---------------------------------| | ANY | /ping/{uuid} | Report successful completion | | ANY | /ping/{uuid}/start | Signal job started | | ANY | /ping/{uuid}/fail | Report failure (immediate alert)| | POST | /ping/{uuid}/log | Attach log output (max 100 KB) | **Examples:** ```bash # Success ping — add to end of your cron job curl -fsS --retry 3 https://watchcron.com/ping/YOUR-UUID # Start signal — add to beginning of your job curl -fsS --retry 3 https://watchcron.com/ping/YOUR-UUID/start # Failure report curl -fsS --retry 3 https://watchcron.com/ping/YOUR-UUID/fail # Log output curl -fsS --retry 3 -X POST \ --data-raw "$(cat /var/log/backup.log | tail -100)" \ https://watchcron.com/ping/YOUR-UUID/log ``` **Recommended cron job pattern:** ```bash #!/bin/bash CHECK_URL="https://watchcron.com/ping/YOUR-UUID" curl -fsS --retry 3 "${CHECK_URL}/start" if /usr/local/bin/backup.sh; then curl -fsS --retry 3 "${CHECK_URL}" else curl -fsS --retry 3 "${CHECK_URL}/fail" fi ``` **Duration tracking:** If you send a `/start` ping followed by a success ping, WatchCron automatically calculates the job duration (visible in ping history as `duration_ms`). --- ## Check Object Reference | Field | Type | Description | |------------------|--------------|------------------------------------------------------| | uuid | string | Unique identifier (UUID v4) | | name | string | Display name | | description | string\|null | Optional description | | status | string | `new`, `up`, `grace`, `down`, or `paused` | | schedule_type | string | `simple` (fixed interval) or `cron` (expression) | | period_seconds | integer\|null| Interval in seconds (simple schedule only) | | cron_expression | string\|null | 5-field cron expression (cron schedule only) | | timezone | string | IANA timezone for schedule evaluation | | grace_seconds | integer | Seconds to wait before alerting on missed ping | | tags | array\|null | Tag strings for organizing checks | | is_paused | boolean | Whether monitoring is paused | | ping_url | string | URL your cron job should call | | last_ping_at | string\|null | ISO 8601 timestamp of most recent ping | | next_expected_at | string\|null | ISO 8601 timestamp of next expected ping | | created_at | string | ISO 8601 timestamp | | updated_at | string | ISO 8601 timestamp | **Status meanings:** - `new` — Waiting for first ping - `up` — Receiving pings on schedule - `grace` — Ping is late but within grace period - `down` — Ping missed, alert triggered - `paused` — Monitoring paused, no alerts --- ## Code Examples ### PHP (Laravel) ```php $response = Http::withToken(env('WATCHCRON_API_KEY')) ->post('https://watchcron.com/api/v1/checks', [ 'name' => 'Nightly Backup', 'schedule_type' => 'cron', 'cron_expression' => '0 2 * * *', 'grace_seconds' => 600, 'tags' => ['production'], ]); $check = $response->json('data'); echo "Ping URL: " . $check['ping_url']; ``` ### Python ```python import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://watchcron.com/api/v1" HEADERS = {"Authorization": f"Bearer {API_KEY}"} response = requests.post( f"{BASE_URL}/checks", headers=HEADERS, json={ "name": "ETL Pipeline", "schedule_type": "cron", "cron_expression": "0 */6 * * *", "grace_seconds": 600, "tags": ["etl"], }, ) check = response.json()["data"] print(f"Ping URL: {check['ping_url']}") ``` ### Node.js ```javascript const response = await fetch("https://watchcron.com/api/v1/checks", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Queue Worker Health", schedule_type: "simple", period_seconds: 300, grace_seconds: 120, }), }); const { data: check } = await response.json(); console.log(`Ping URL: ${check.ping_url}`); ``` ### Go ```go body := strings.NewReader(`{ "name": "Log Rotation", "schedule_type": "cron", "cron_expression": "0 0 * * 0", "grace_seconds": 1800 }`) req, _ := http.NewRequest("POST", "https://watchcron.com/api/v1/checks", body) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://watchcron.com/api/v1/checks") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer YOUR_API_KEY" request["Content-Type"] = "application/json" request.body = { name: "Sidekiq Health", schedule_type: "simple", period_seconds: 600, grace_seconds: 180, }.to_json response = http.request(request) check = JSON.parse(response.body)["data"] puts "Ping URL: #{check['ping_url']}" ``` ### Laravel Scheduler ```php // app/Console/Kernel.php $schedule->command('backup:run') ->daily() ->pingBeforeIf(true, 'https://watchcron.com/ping/YOUR-UUID/start') ->thenPing('https://watchcron.com/ping/YOUR-UUID') ->pingOnFailure('https://watchcron.com/ping/YOUR-UUID/fail'); ``` --- ## Pricing | Plan | Price | Checks | Rate Limit | Channels | |----------|-------------|--------|---------------|----------------------------------------| | Free | $0/mo | 20 | 100 req/min | Email, Webhook | | Starter | $7/mo | 75 | 200 req/min | + Slack, Telegram, Discord, Teams | | Pro | $19/mo | 250 | 500 req/min | + SMS, PagerDuty, OpsGenie | | Business | $49/mo | 1,000 | 1,000 req/min | + Voice | Yearly billing saves ~20%. Port and domain monitors are unlimited and don't count against check limits. --- ## Links - Dashboard: https://watchcron.com/app - Documentation: https://watchcron.com/docs/api - OpenAPI spec: https://watchcron.com/openapi.json - Status page: https://status.watchcron.com