Checks

Checks are the core resource in WatchCron. Each check represents a cron job or scheduled task you want to monitor. The API lets you create, list, update, and delete checks programmatically.

List checks

GET /api/v1/checks

Returns a paginated list of all checks in the project associated with your API key.

Query parameters

ParameterTypeDefaultDescription
tagstring—Filter checks by tag (exact match)
per_pageinteger25Results per page
pageinteger1Page number
curl -H "Authorization: Bearer YOUR_API_KEY" \
     "https://watchcron.com/api/v1/checks?tag=production&per_page=10"
$response = Http::withToken('YOUR_API_KEY')
    ->get('https://watchcron.com/api/v1/checks', [
        'tag'      => 'production',
        'per_page' => 10,
    ]);

$checks = $response->json('data');
import requests

response = requests.get(
    "https://watchcron.com/api/v1/checks",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={"tag": "production", "per_page": 10},
)

checks = response.json()["data"]
const response = await fetch(
  "https://watchcron.com/api/v1/checks?tag=production&per_page=10",
  { headers: { "Authorization": "Bearer YOUR_API_KEY" } }
);

const { data: checks } = await response.json();
req, _ := http.NewRequest("GET",
    "https://watchcron.com/api/v1/checks?tag=production&per_page=10", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

Response 200 OK

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

Check object

Every check response includes these fields:

FieldTypeDescription
uuidstringUnique identifier (UUID v4). Used in all API and ping URLs.
namestringDisplay name of the check
descriptionstring|nullOptional description
statusstringCurrent status: new, up, grace, down, or paused
schedule_typestringsimple (fixed interval) or cron (cron expression)
period_secondsinteger|nullInterval in seconds (only for simple schedule)
cron_expressionstring|nullCron expression (only for cron schedule). Supports standard 5-field format.
timezonestringIANA timezone for cron schedule evaluation (e.g. America/New_York)
grace_secondsintegerSeconds to wait after expected ping before alerting
tagsarray|nullList of tag strings for organizing checks
is_pausedbooleanWhether monitoring is paused
ping_urlstringThe URL your cron job should ping on completion
last_ping_atstring|nullISO 8601 timestamp of most recent ping
next_expected_atstring|nullISO 8601 timestamp of next expected ping
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp

Status values

StatusMeaning
newCheck created, waiting for the first ping
upReceiving pings on schedule — everything is healthy
gracePing is late, but still within the grace period
downPing missed — grace period expired, alert triggered
pausedMonitoring is paused — no alerts will fire

Create a check

POST /api/v1/checks

Request body

FieldTypeRequiredDescription
namestringYesCheck name (max 255 characters)
descriptionstringNoOptional description (max 1000 characters)
schedule_typestringYessimple or cron
period_secondsintegerIf simpleInterval in seconds (minimum 60)
cron_expressionstringIf cronStandard 5-field cron expression (max 100 characters)
timezonestringNoIANA timezone (defaults to UTC)
grace_secondsintegerNoGrace period in seconds (defaults to system default)
tagsarrayNoList of tag strings (each max 50 characters)
Schedule types: Use simple for fixed-interval jobs (e.g. “every 5 minutes”). Use cron when your job runs on a specific cron schedule (e.g. 0 2 * * * for 2 AM daily). The cron type supports standard 5-field expressions including ranges, lists, and steps.

Example: create with cron schedule

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"]
     }'
$response = Http::withToken('YOUR_API_KEY')
    ->post('https://watchcron.com/api/v1/checks', [
        'name'            => 'Nightly Backup',
        'schedule_type'   => 'cron',
        'cron_expression' => '0 2 * * *',
        'timezone'        => 'America/New_York',
        'grace_seconds'   => 600,
        'tags'            => ['production', 'database'],
    ]);

$check = $response->json('data');
echo "Ping URL: " . $check['ping_url'];
import requests

response = requests.post(
    "https://watchcron.com/api/v1/checks",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "name": "Nightly Backup",
        "schedule_type": "cron",
        "cron_expression": "0 2 * * *",
        "timezone": "America/New_York",
        "grace_seconds": 600,
        "tags": ["production", "database"],
    },
)

check = response.json()["data"]
print(f"Ping URL: {check['ping_url']}")
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: "Nightly Backup",
    schedule_type: "cron",
    cron_expression: "0 2 * * *",
    timezone: "America/New_York",
    grace_seconds: 600,
    tags: ["production", "database"],
  }),
});

const { data: check } = await response.json();
console.log(`Ping URL: ${check.ping_url}`);
body := strings.NewReader(`{
  "name": "Nightly Backup",
  "schedule_type": "cron",
  "cron_expression": "0 2 * * *",
  "timezone": "America/New_York",
  "grace_seconds": 600,
  "tags": ["production", "database"]
}`)

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()

Response 201 Created

JSON
{
  "data": {
    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Nightly Backup",
    "description": null,
    "status": "new",
    "schedule_type": "cron",
    "period_seconds": null,
    "cron_expression": "0 2 * * *",
    "timezone": "America/New_York",
    "grace_seconds": 600,
    "tags": ["production", "database"],
    "is_paused": false,
    "ping_url": "https://watchcron.com/ping/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "last_ping_at": null,
    "next_expected_at": null,
    "created_at": "2026-09-14T10:00:00Z",
    "updated_at": "2026-09-14T10:00:00Z"
  }
}

Example: create with simple interval

POST /api/v1/checks
curl -X POST https://watchcron.com/api/v1/checks \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "Health Check Ping",
       "schedule_type": "simple",
       "period_seconds": 300,
       "grace_seconds": 120
     }'

This creates a check that expects a ping every 5 minutes (300 seconds) with a 2-minute grace period.

Validation errors

If the cron expression is invalid, the API returns 422:

422 response
{
  "error": "Invalid cron expression"
}

Get a check

GET /api/v1/checks/{uuid}

Returns full details for a single check.

curl -H "Authorization: Bearer YOUR_API_KEY" \
     "https://watchcron.com/api/v1/checks/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
$uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

$response = Http::withToken('YOUR_API_KEY')
    ->get("https://watchcron.com/api/v1/checks/{$uuid}");

$check = $response->json('data');
uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

response = requests.get(
    f"https://watchcron.com/api/v1/checks/{uuid}",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)

check = response.json()["data"]
const uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";

const response = await fetch(
  `https://watchcron.com/api/v1/checks/${uuid}`,
  { headers: { "Authorization": "Bearer YOUR_API_KEY" } }
);

const { data: check } = await response.json();

Returns the same check object as shown in Check object. Returns 404 if the UUID does not exist or belongs to a different project.

Update a check

PUT /api/v1/checks/{uuid}

Updates one or more fields on an existing check. Only include the fields you want to change — all fields are optional.

Request body

FieldTypeDescription
namestringCheck name (max 255 characters)
descriptionstring|nullOptional description (max 1000 characters)
schedule_typestringsimple or cron
period_secondsinteger|nullInterval in seconds (min 60)
cron_expressionstring|nullCron expression (max 100 characters)
timezonestring|nullIANA timezone
grace_secondsinteger|nullGrace period in seconds
tagsarray|nullTag strings (replaces existing tags)
curl -X PUT https://watchcron.com/api/v1/checks/YOUR-UUID \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "Nightly Backup v2",
       "cron_expression": "30 1 * * *",
       "grace_seconds": 900
     }'
$response = Http::withToken('YOUR_API_KEY')
    ->put("https://watchcron.com/api/v1/checks/{$uuid}", [
        'name'            => 'Nightly Backup v2',
        'cron_expression' => '30 1 * * *',
        'grace_seconds'   => 900,
    ]);
response = requests.put(
    f"https://watchcron.com/api/v1/checks/{uuid}",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "name": "Nightly Backup v2",
        "cron_expression": "30 1 * * *",
        "grace_seconds": 900,
    },
)
const response = await fetch(`https://watchcron.com/api/v1/checks/${uuid}`, {
  method: "PUT",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Nightly Backup v2",
    cron_expression: "30 1 * * *",
    grace_seconds: 900,
  }),
});

Returns 200 OK with the updated check object.

Note on tags: Sending "tags": ["new-tag"] replaces all existing tags. To add a tag, include the full list of desired tags. Send "tags": null to clear all tags.

Delete a check

DELETE /api/v1/checks/{uuid}

Permanently deletes a check and all its associated ping data.

curl -X DELETE \
     -H "Authorization: Bearer YOUR_API_KEY" \
     "https://watchcron.com/api/v1/checks/YOUR-UUID"
$response = Http::withToken('YOUR_API_KEY')
    ->delete("https://watchcron.com/api/v1/checks/{$uuid}");
response = requests.delete(
    f"https://watchcron.com/api/v1/checks/{uuid}",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
const response = await fetch(
  `https://watchcron.com/api/v1/checks/${uuid}`,
  {
    method: "DELETE",
    headers: { "Authorization": "Bearer YOUR_API_KEY" },
  }
);

Returns 204 No Content on success. This action is irreversible.