Monitors

A monitor is one thing being watched and its current verdict. Health checks and heartbeats are the same resource here, separated by type, because that is how they are read: one list answering whether anything is wrong right now. On this page we will go through the endpoints for creating, reading and removing them.

AUTH Authorization: Bearer {API_KEY} Create a token →

The monitor model #

id integer

Unique identifier for the monitor.

type string

http for a health check, heartbeat for a job that reports in. Fixed at creation.

name string

What the monitor is called in the Console and in notifications.

is_active boolean

Whether the monitor is being checked. An inactive monitor holds no place against your plan’s limit.

current_status string

pending until the first verdict, then up or down.

monitorable_type string

The kind of thing being watched: App\Models\Project or App\Models\Domain for a health check, App\Models\SchedulerJob or App\Models\BackgroundJob for a heartbeat.

monitorable_id integer

The id of that thing. Fixed at creation, along with monitorable_type.

config object

Type-specific settings — see the two tables below.

ping_url string

Heartbeats only: the address the watched job reports to. Depfloy puts it into the job for you; it is here so you can see what was installed.

last_checked_at timestamp

When the monitor was last evaluated.

last_ping_at timestamp

Heartbeats only: when a report last arrived.

last_status_changed_at timestamp

When current_status last changed. This is what “down for 20 minutes” is measured from.

uptime_24h number

Percentage of the last 24 hours the monitor was up, or null when there is not enough history. Present on list and read responses.

uptime_7d number

The same figure over the last 7 days.

Health check config #

url string

The address to request.

method string

GET, HEAD or POST. Defaults to GET.

expected_status integer

The status that counts as healthy, between 100 and 599. Omit it and any 2xx passes. Redirects are not followed, so a URL that answers 301 fails unless you ask for 301 here.

interval_seconds integer

How often to check, up to 86400. The fastest value depends on the plan — 60 on Pro and Business, 300 on Starter — and GET /api/v1/monitors/options reports the range yours allows. Asking for less is refused with a 422. A monitor already set faster than its plan permits keeps the value and is checked at the plan’s frequency.

timeout_seconds integer

How long to wait for an answer, from 1 to 60. Defaults to 10.

confirmation_threshold integer

Consecutive failures before the monitor is reported down, from 1 to 10. Defaults to 3.

Heartbeat config #

Every field is optional. For a scheduled job the timing is read from its cron expression, so the common create carries no config at all.

expected_period_seconds integer

How often a report is expected, from 60 to 2592000. Derived from the job’s schedule when you omit it. Supply it for a job that usually finishes in seconds but occasionally takes an hour — nobody but its owner knows that.

grace_seconds integer

How late a report may be before it counts against the monitor, from 30 to 86400. For a scheduled job it defaults to half the period and never less than 60; a background job, whose listener reports every minute, defaults to 150.

confirmation_threshold integer

Consecutive failed evaluations before the monitor is reported down, from 1 to 10. Defaults to 3.


GET/api/v1/monitors

List monitors #

Returns the monitors you can reach, 25 per page, with the ones that are down first — the question a monitor list answers is whether anything is wrong right now, and an alphabetical sort buries that.

Requires monitor:read.

Optional attributes #

type string

Narrow to http or heartbeat.

status string

Narrow to up, down or pending.

page integer

Page to fetch.

GET /api/v1/monitors Request
cURL
curl https://app.depfloy.com/api/v1/monitors?status=down \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json"
Response
{
  "current_page": 1,
  "data": [
    {
      "id": 14,
      "type": "heartbeat",
      "name": "Nightly invoices",
      "is_active": true,
      "current_status": "down",
      "monitorable_type": "App\\Models\\SchedulerJob",
      "monitorable_id": 87,
      "config": {
        "expected_period_seconds": 86400,
        "grace_seconds": 43200
      },
      "ping_url": "https://app.depfloy.com/api/ping/9f2c…",
      "last_ping_at": "2026-08-23T02:00:04+00:00",
      "last_status_changed_at": "2026-08-24T14:03:11+00:00",
      "uptime_24h": 41.75,
      "uptime_7d": 91.68
    }
  ],
  "per_page": 25,
  "total_items": 1,
  "total_pages": 1
}

GET/api/v1/monitors/:id

Read a monitor #

Returns one monitor with its target and its 50 most recent events, newest first. An event is written when the status changes, so this is the outage history rather than a log of every check.

Each event carries a status of down or up, the message explaining it, a context object with whatever the check saw — the HTTP status, the latency, the error — and resolved_at, which is filled in on the outage when it ends.

Requires monitor:read. A monitor in another organization, or one whose project you cannot reach, answers 404.

GET /api/v1/monitors/14 Request
cURL
curl https://app.depfloy.com/api/v1/monitors/14 \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json"
Response
{
  "status": "success",
  "data": {
    "id": 14,
    "type": "heartbeat",
    "name": "Nightly invoices",
    "current_status": "down",
    "events": [
      {
        "id": 502,
        "status": "down",
        "message": "No report since 2026-08-23 02:00:04 UTC.",
        "context": { "expected_period_seconds": 86400 },
        "resolved_at": null,
        "created_at": "2026-08-24T14:03:11+00:00"
      }
    ]
  }
}

POST/api/v1/monitors

Create a monitor #

Creates a health check or a heartbeat. The new monitor starts at pending and stays quiet until it has enough evidence for a verdict.

Creating a heartbeat also installs the reporting: a scheduled job’s crontab line is rewritten to report in, and a server that gains its first watched background job gains the listener that reports for it.

Requires monitor:manage. A target you cannot reach answers 404, and exceeding your plan’s limit for that type answers 403 with the number you are allowed.

Attributes #

type string

http or heartbeat.

name string

Up to 100 characters.

monitorable_type string

What to watch. A health check takes App\Models\Project or App\Models\Domain; a heartbeat takes App\Models\SchedulerJob or App\Models\BackgroundJob.

monitorable_id integer

The id of that record.

config object

Required for a health check, optional for a heartbeat. See the tables above.

is_active boolean

Defaults to true.

POST /api/v1/monitors Request
cURL
curl -X POST https://app.depfloy.com/api/v1/monitors \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "http",
    "name": "Storefront",
    "monitorable_type": "App\\Models\\Project",
    "monitorable_id": 412,
    "config": {
      "url": "https://shop.example.com",
      "expected_status": 200,
      "interval_seconds": 300
    }
  }'
Response
Success
{
  "status": "success",
  "message": "Monitor created",
  "data": {
    "id": 15,
    "type": "http",
    "name": "Storefront",
    "current_status": "pending",
    "is_active": true
  }
}
Over the limit
{
  "status": "error",
  "message": "Your plan allows 1 active Health Check monitor(s). Turn one off or upgrade your plan to add another."
}

PUT/api/v1/monitors/:id

Update a monitor #

Changes name, config, notification_channels or is_active.

type, monitorable_type and monitorable_id are rejected. They are settled at creation: a monitor pointed at something else would carry an event history describing two different things as though they were one.

Turning a monitor back on returns it to pending and clears its counters, so its first check after the gap is judged on its own evidence rather than on failures from weeks ago. It also has to fit your plan’s limit again.

Requires monitor:manage.

PUT /api/v1/monitors/15 Request
cURL
curl -X PUT https://app.depfloy.com/api/v1/monitors/15 \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": false
  }'
Response
{
  "status": "success",
  "message": "Monitor updated",
  "data": {
    "id": 15,
    "is_active": false
  }
}

DELETE/api/v1/monitors/:id

Delete a monitor #

Removes the monitor and its events. For a heartbeat this also takes the reporting back out: the crontab line is rewritten without it, and a server whose last watched background job is removed loses the listener.

Requires monitor:manage.

DELETE /api/v1/monitors/15 Request
cURL
curl -X DELETE https://app.depfloy.com/api/v1/monitors/15 \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json"
Response
{
  "status": "success",
  "message": "Monitor deleted"
}

GET/api/v1/monitors/options

Monitor options #

Returns the monitor types and the targets each one accepts, so a client can build a create form without hardcoding the catalog.

It also reports what the caller’s own plan allows: interval.min_seconds, interval.max_seconds and interval.default_seconds for the check frequency, and monitor_limit for how many active monitors of one type the plan covers (null when unlimited). Reading them here keeps a client from hardcoding numbers that differ per plan.

Requires monitor:read.

GET /api/v1/monitors/options Request
cURL
curl https://app.depfloy.com/api/v1/monitors/options \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Accept: application/json"