Job monitors
Alerts you when a scheduled job misses its check-in, runs too long, or fails. $0.50 per monitor per month. The base URL is https://www.motor.dev/job_monitors, and the OpenAPI 3.1 specification describes each endpoint.
Sign in to try the API here with one of your team's keys. Requests use that key's permissions and spending limit. Standard charges apply.
Quick start
Know when your scheduled jobs need attention. Give each job a monitor, then have it ping its secret URL whenever it runs. If a ping doesn't arrive on time, Motor alerts you.
First, choose where alerts should go. This example adds an email channel. Open the confirmation link sent to the address if it belongs to someone outside your team. New monitors automatically use channels with "default": true.
curl https://api.motor.dev/job_monitors/alert_channels \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-d '{"kind": "email", "address": "ops@example.com", "default": true}'
Create a monitor. This example expects a daily backup at 03:00 New York time, with a 10-minute grace period:
curl https://api.motor.dev/job_monitors/monitors \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Nightly backup", "schedule": "0 3 * * *",
"time_zone": "America/New_York", "grace_seconds": 600}'
Copy the ping_url from the response and use it in your crontab to send a ping when the backup succeeds. Set the cron scheduler to the same time zone as the monitor:
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 https://api.motor.dev/job_monitors/ping/0b1f8c2e-...
Monitoring starts as soon as you create the monitor, so you'll hear about a missed run even if the job has never sent a ping.
Schedules and grace periods
Choose the schedule that matches your job:
- Cron.
schedule is a five-field cron expression, such as 0 3 * * *, or a macro, such as @daily. time_zone is an IANA time zone name, such as America/New_York. It defaults to UTC. - Interval.
interval_seconds, from 60 seconds to 31 days. The next ping is due one interval after the last ping that started a run.
grace_seconds gives your job extra time after its scheduled check-in before Motor marks the run as missed. The default is 5 minutes. Pings can also arrive up to 30 seconds early: a ping at 02:59:58 counts for a 03:00 run.
When daylight saving time skips a scheduled time, Motor shifts it forward by the clock change: for a one-hour change, 02:30 becomes 03:30. When a time occurs twice, Motor expects only one run, at the first occurrence.
Runs
Send a start ping when the job starts, then a success or failure ping when it finishes. Motor tracks the duration and alerts you if the run exceeds max_runtime_seconds.
URL=https://api.motor.dev/job_monitors/ping/0b1f8c2e-...
RID=$(uuidgen)
curl -fsS "$URL/start?rid=$RID"
/usr/local/bin/backup.sh
curl -fsS "$URL/$?"?rid=$RID # the exit code: 0 is a success
/start opens a run. /fail, /<exit code>, and the plain URL close it. /log adds a log message without changing the monitor's state.?rid= is a UUID that you choose for each execution. With run IDs, runs can overlap, and repeated pings don't count twice. Without a run ID, a second start abandons the open run.- A POST body of any content type becomes the ping's log. Motor keeps the first 100,000 bytes. To send output from a shell, use
--data-binary: curl -d @file removes the newlines.
Statuses and incidents
Each monitor has one status:
failure_tolerance sets how many consecutive unsuccessful results to allow before opening an incident. The default is 0, so the first unsuccessful result opens one. recovery_threshold sets how many consecutive successful results are needed to resolve it. realert_seconds sets the interval between reminders while an incident is open.
Ping from your jobs
The ping URLs work with clients for Healthchecks.io. Use https://api.motor.dev/job_monitors/ping as the base URL.
runitor
runitor -api-url https://api.motor.dev/job_monitors/ping \
-uuid 0b1f8c2e-... -- /usr/local/bin/backup.sh
borgmatic
healthchecks:
ping_url: https://api.motor.dev/job_monitors/ping/0b1f8c2e-...
Kubernetes CronJob
containers:
- name: report
image: example/report:1.4
command: ["/bin/sh", "-c"]
args:
- >
curl -fsS "$PING_URL/start" &&
/app/report;
curl -fsS "$PING_URL/$?"
Logs with curl
/usr/local/bin/backup.sh 2>&1 | curl -fsS --data-binary @- \
-H "Content-Type: text/plain" "$PING_URL/$?"
Ping keys. A ping key lets a script send pings by slug: /ping/<ping key>/<slug>. With ?create=1 and a key that has can_create enabled, the first ping creates the monitor.
Alerts and webhooks
Get alerts by email, webhook, Slack, Discord, or PagerDuty when an incident opens or resolves, plus optional reminders while it's open. Email recipients outside your team must confirm their address first. Enable include_logs on a channel to include the last 2,000 bytes of the failed ping's log.
Motor sends each webhook event as a JSON POST request. The Motor-Signature header contains a timestamp and an HMAC-SHA256 signature of <time>.<body>. Verify the signature and reject events with timestamps more than 5 minutes from your clock. During secret rotation, the header includes one v1 signature for each secret.
Motor-Event-Id: evt_jma_3XzPd6LcVhT
Motor-Signature: t=1790000000,v1=5d3c...
{"id": "evt_jma_3XzPd6LcVhT", "type": "job_monitors.incident.opened",
"created_at": "2026-10-05T07:10:00Z",
"data": {"incident": {...}, "monitor": {...}, "message": "..."}}
Check a signature in Python
import base64, hashlib, hmac, time
def verified(secret, header, body):
key = base64.urlsafe_b64decode(secret.removeprefix("whsec_") + "==")
parts = [part.split("=", 1) for part in header.split(",")]
t = next(value for name, value in parts if name == "t")
if abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(key, f"{t}.{body}".encode(), hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, value) for name, value in parts if name == "v1")
Check a signature in Node.js
import crypto from "node:crypto";
export function verified(secret, header, body) {
const key = Buffer.from(secret.replace("whsec_", ""), "base64url");
const parts = header.split(",").map((part) => part.split("="));
const t = parts.find(([name]) => name === "t")?.[1];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac("sha256", key).update(`${t}.${body}`).digest("hex");
return parts.some(([name, value]) => name === "v1" && value.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)));
}
Monitors as code
Keep monitor definitions alongside your job code. POST /monitors/sync creates and updates monitors by slug. With prune_tag, it also deletes monitors with that tag whose slugs are missing from the request. Motor validates the entire request before making changes. Set dry_run to true to preview the changes.
curl https://api.motor.dev/job_monitors/monitors/sync \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-d '{"monitors": [{"slug": "nightly-backup", "name": "Nightly backup",
"schedule": "0 3 * * *", "tags": ["deploy:api"]}],
"prune_tag": "deploy:api", "dry_run": true}'
Pricing and limits
$0.50 per monitor per month, prorated daily. Monitors paused for the entire day are free for that day. Alerts are included at no extra cost.
Once an API key reaches its spending limit, it can't create more monitors. Existing monitors keep running and accruing charges.
Ping endpoints
POST/job_monitors/ping/{token}
Report a successful run
Records a success ping. Use GET, HEAD, or POST. A POST body (the first 100,000 bytes) becomes the ping's log. Use the same ?rid=<uuid> on start and completion pings to link them to one run.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
POST/job_monitors/ping/{token}/start
Report the start of a run
Records a start ping. Use GET, HEAD, or POST. A POST body (the first 100,000 bytes) becomes the ping's log. Use the same ?rid=<uuid> on start and completion pings to link them to one run. A start opens a run. A run that doesn't complete before its maximum runtime times out.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
POST/job_monitors/ping/{token}/fail
Report a failed run
Records a fail ping. Use GET, HEAD, or POST. A POST body (the first 100,000 bytes) becomes the ping's log. Use the same ?rid=<uuid> on start and completion pings to link them to one run.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
POST/job_monitors/ping/{token}/log
Send a log message
Records a log ping. It doesn't change the monitor's state.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
POST/job_monitors/ping/{token}/{exit_code}
Report an exit code
Records the exit code of a run, from 0 to 255. 0 is a success. Other codes are failures.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
POST/job_monitors/ping/{ping_key}/{slug}
Send a ping by monitor slug
Send pings using a ping key and a monitor slug. Add /start, /fail, /log, or an exit code to report the result. With create=1, Motor creates the monitor if the slug doesn't exist and the ping key has can_create enabled. Use schedule, interval, tz, and grace to configure the new monitor.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Responses
200Motor recorded the ping.
OK
201Motor created the monitor and recorded the ping.
Created
403The monitor couldn't be created. The response body explains why.
forbidden: this ping key can't create monitors
400The path or a parameter is incorrect.
invalid url format
404The token doesn't exist.
not found
429The monitor exceeded its ping limit: 4 pings per minute, with bursts of up to 20.
rate limited
Monitors
POST/job_monitors/monitors
Create a monitor
Creates a monitor and starts checking its schedule immediately. Motor can alert you to a missed run even if the job has never sent a ping.
Request body{
"name": "Nightly backup",
"schedule": "0 3 * * *",
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"tags": [
"db",
"prod"
]
}
Responses
201The monitor.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
402The key has reached its monthly spending limit.
{
"error": {
"code": "limit_reached",
"message": "This key reached its monthly spending limit, so it can't create monitors. Existing monitors keep running. Raise the limit on the API keys page."
}
}
409A monitor in your team already uses this slug.
{
"error": {
"code": "slug_taken",
"message": "Another monitor of the team has the slug `nightly-backup`."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
GET/job_monitors/monitors
List monitors
Lists the team's monitors, oldest first.
Responses
200A page of monitors.
{
"data": [
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
GET/job_monitors/monitors/{id}
Read a monitor
Returns a monitor with its status: paused, down, late, new, or up.
Responses
200The monitor.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
PATCH/job_monitors/monitors/{id}
Update a monitor
Updates only the fields included in the request. Changing the schedule recalculates the next check-in from the current time. To switch between a cron schedule and an interval, set the previous schedule field to null.
Request body{
"grace_seconds": 900,
"schedule": "0 4 * * *"
}
Responses
200The monitor.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
DELETE/job_monitors/monitors/{id}
Delete a monitor
Deletes the monitor and its pings, runs, and incidents. Billing records are retained.
Responses
200The monitor is deleted.
{
"id": "jmm_4ZpQ7bLxN2c",
"deleted": true
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
POST/job_monitors/monitors/{id}/pause
Pause a monitor
Pauses monitoring until the until timestamp, the next ping, or an explicit resume request. Motor abandons active runs and resolves any open incident. Monitors paused for an entire day are free for that day.
Request body{
"until": "2026-10-06T12:00:00Z"
}
Responses
200The monitor.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "paused",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": null,
"paused_until": "2026-10-06T12:00:00Z",
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
POST/job_monitors/monitors/{id}/resume
Resume a monitor
Resumes a paused monitor and calculates its next check-in from the current time.
Responses
200The monitor.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
POST/job_monitors/monitors/{id}/rotate_token
Rotate the ping token
Creates a new ping URL for the monitor. The previous URL remains valid for overlap_seconds. Set this to 0 to immediately invalidate all previous ping URLs.
Request body{
"overlap_seconds": 86400
}
Responses
200The monitor with its new ping_url.
{
"id": "jmm_4ZpQ7bLxN2c",
"name": "Nightly backup",
"slug": "nightly-backup",
"tags": [
"db",
"prod"
],
"schedule": "0 3 * * *",
"interval_seconds": null,
"time_zone": "America/New_York",
"grace_seconds": 600,
"max_runtime_seconds": 3600,
"failure_tolerance": 0,
"recovery_threshold": 1,
"realert_seconds": 86400,
"alert_channel_ids": [
"jmc_9kTe2WvRmQa"
],
"status": "up",
"down_reason": null,
"running": false,
"ping_url": "https://api.motor.dev/job_monitors/ping/0b1f8c2e-6d1a-4f6b-9c3e-2a7d5e8f1b40",
"last_ping_at": "2026-10-05T07:05:14Z",
"last_duration_seconds": 312,
"next_due_at": "2026-10-06T07:00:00Z",
"paused_until": null,
"created_at": "2026-10-01T18:22:05Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
POST/job_monitors/monitors/sync
Sync monitors
Creates and updates monitors by slug for deployment scripts and agents. Set prune_tag to delete monitors with that tag whose slugs are missing from the request. Motor validates the entire request before making changes. Set dry_run to true to preview the changes.
Request body{
"monitors": [
{
"slug": "nightly-backup",
"name": "Nightly backup",
"schedule": "0 3 * * *",
"tags": [
"deploy:api"
]
}
],
"prune_tag": "deploy:api",
"dry_run": false
}
Responses
200The monitors created, updated, and deleted, or the proposed changes for a dry run.
{
"created": [
"nightly-backup"
],
"updated": [],
"unchanged": [],
"deleted": [
"old-report"
],
"dry_run": false
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
Runs and pings
GET/job_monitors/monitors/{monitor_id}/runs
List runs
Lists the runs of a monitor, newest first. result is running, succeeded, failed, timed_out, or abandoned.
Responses
200A page of runs.
{
"data": [
{
"id": "jmr_7GhLs3NqPzE",
"rid": "6f1c2b8e-1d2a-4c4e-9a63-1f0f9b3c2d10",
"occurrence_at": "2026-10-05T07:00:00Z",
"started_at": "2026-10-05T07:00:02Z",
"finished_at": "2026-10-05T07:05:14Z",
"result": "succeeded",
"exit_code": 0,
"duration_seconds": 312
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
GET/job_monitors/monitors/{monitor_id}/pings
List pings
Lists the pings of a monitor, newest first.
Responses
200A page of pings.
{
"data": [
{
"id": "jmp_2YcVbN8xKfM",
"kind": "success",
"received_at": "2026-10-05T07:05:14Z",
"rid": "6f1c2b8e-1d2a-4c4e-9a63-1f0f9b3c2d10",
"run_id": "jmr_7GhLs3NqPzE",
"exit_code": 0,
"method": "POST",
"user_agent": "runitor/v1.3.0",
"body_bytes": 18213,
"body_truncated": false,
"body_stored": true
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
GET/job_monitors/monitors/{monitor_id}/pings/{id}/body
Download a ping body
Returns the stored body of a ping as plain text. Motor keeps the bodies of the newest 100 pings of each monitor.
Responses
200The body.
Backup finished: 1,204 files, 3.2 GB.
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The ping or its body doesn't exist.
{
"error": {
"code": "not_found",
"message": "The ping body doesn't exist."
}
}
Incidents
GET/job_monitors/monitors/{monitor_id}/incidents
List a monitor's incidents
Lists the incidents of a monitor, newest first.
Responses
200A page of incidents.
{
"data": [
{
"id": "jmi_5RmWq1JtBnD",
"monitor_id": "jmm_4ZpQ7bLxN2c",
"reason": "missed",
"opened_at": "2026-10-05T07:10:00Z",
"resolved_at": null
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
GET/job_monitors/incidents
List incidents
Lists the team's incidents, newest first.
Responses
200A page of incidents.
{
"data": [
{
"id": "jmi_5RmWq1JtBnD",
"monitor_id": "jmm_4ZpQ7bLxN2c",
"reason": "missed",
"opened_at": "2026-10-05T07:10:00Z",
"resolved_at": null
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
GET/job_monitors/incidents/{id}
Read an incident
Returns an incident and the delivery status of each alert. Alert status is pending, sending, delivered, failed, or superseded.
Responses
200The incident.
{
"id": "jmi_5RmWq1JtBnD",
"monitor_id": "jmm_4ZpQ7bLxN2c",
"reason": "missed",
"opened_at": "2026-10-05T07:10:00Z",
"resolved_at": null,
"alerts": [
{
"id": "jma_3XzPd6LcVhT",
"channel_id": "jmc_9kTe2WvRmQa",
"kind": "opened",
"status": "delivered",
"attempts": 1,
"sent_at": "2026-10-05T07:10:04Z",
"error": null
}
]
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
Alert channels
POST/job_monitors/alert_channels
Create an alert channel
Creates a destination for alerts. For webhooks, save the signing_secret from the response; it is shown only once.
Request body{
"kind": "webhook",
"url": "https://hooks.example.com/motor",
"default": true
}
Responses
201The channel.
{
"id": "jmc_9kTe2WvRmQa",
"kind": "webhook",
"name": "Ops webhook",
"url": "https://hooks.example.com/motor",
"default": true,
"include_logs": false,
"confirmed": true,
"created_at": "2026-10-01T18:20:00Z",
"signing_secret": "whsec_Q2hhbmdlIHRoaXMgc2VjcmV0IGJlZm9yZSB1c2luZyBpdA"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
429The team sent 10 confirmation emails in the last 24 hours.
{
"error": {
"code": "rate_limited",
"message": "The team sent 10 confirmation emails in the last 24 hours. Try again later, or use the address of a team member."
}
}
GET/job_monitors/alert_channels
List alert channels
Lists the team's channels. Credentials are hidden.
Responses
200A page of channels.
{
"data": [
{
"id": "jmc_9kTe2WvRmQa",
"kind": "webhook",
"name": "Ops webhook",
"url": "https://hooks.example.com/motor",
"default": true,
"include_logs": false,
"confirmed": true,
"created_at": "2026-10-01T18:20:00Z"
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
GET/job_monitors/alert_channels/{id}
Read an alert channel
Returns a channel.
Responses
200The channel.
{
"id": "jmc_9kTe2WvRmQa",
"kind": "webhook",
"name": "Ops webhook",
"url": "https://hooks.example.com/motor",
"default": true,
"include_logs": false,
"confirmed": true,
"created_at": "2026-10-01T18:20:00Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
PATCH/job_monitors/alert_channels/{id}
Update an alert channel
Changes name, default, and include_logs. To change the destination, delete the channel and add a new one.
Request body{
"include_logs": true
}
Responses
200The channel.
{
"id": "jmc_9kTe2WvRmQa",
"kind": "webhook",
"name": "Ops webhook",
"url": "https://hooks.example.com/motor",
"default": true,
"include_logs": true,
"confirmed": true,
"created_at": "2026-10-01T18:20:00Z"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
422A field is invalid.
{
"error": {
"code": "invalid_field",
"message": "`grace_seconds` must be from 60 to 2678400."
}
}
DELETE/job_monitors/alert_channels/{id}
Delete an alert channel
Removes the channel from all monitors and cancels pending alerts for that channel.
Responses
200The channel is deleted.
{
"id": "jmc_9kTe2WvRmQa",
"deleted": true
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
POST/job_monitors/alert_channels/{id}/test
Send a test alert
Sends a test alert to the channel. At most 10 per channel per hour.
Responses
202The test alert, queued for delivery.
{
"id": "jma_3XzPd6LcVhT",
"channel_id": "jmc_9kTe2WvRmQa",
"kind": "test",
"status": "pending",
"attempts": 0,
"sent_at": null,
"error": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
429The channel sent 10 test alerts in the last hour.
{
"error": {
"code": "rate_limited",
"message": "The channel sent 10 test alerts in the last hour. Try again later."
}
}
POST/job_monitors/alert_channels/{id}/rotate_secret
Rotate a signing secret
Creates a new webhook signing secret. For the next 24 hours, deliveries include signatures from both the old and new secrets so you can update your integration.
Responses
200The channel with its new signing_secret.
{
"id": "jmc_9kTe2WvRmQa",
"kind": "webhook",
"name": "Ops webhook",
"url": "https://hooks.example.com/motor",
"default": true,
"include_logs": false,
"confirmed": true,
"created_at": "2026-10-01T18:20:00Z",
"signing_secret": "whsec_TmV3IHNlY3JldCBhZnRlciBhIHJvdGF0aW9uIGFiYw"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}
Ping keys
POST/job_monitors/ping_keys
Create a ping key
Creates a ping key for sending pings by monitor slug. Save the key from the response; it is shown only once. Enable can_create to allow pings with create=1 to create monitors.
Request body{
"can_create": true
}
Responses
201The ping key.
{
"id": "jmk_6TnBv2QwLsJ",
"prefix": "pk3X",
"can_create": true,
"created_at": "2026-10-01T18:25:00Z",
"key": "pk3Xz8QvL2mN5rT7wY9aBc"
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
GET/job_monitors/ping_keys
List ping keys
Lists your team's ping keys. Secret values are omitted.
Responses
200A page of ping keys.
{
"data": [
{
"id": "jmk_6TnBv2QwLsJ",
"prefix": "pk3X",
"can_create": true,
"created_at": "2026-10-01T18:25:00Z"
}
],
"next_cursor": null
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
DELETE/job_monitors/ping_keys/{id}
Delete a ping key
Deletes a ping key. Any slug-based ping URLs using this key stop working immediately.
Responses
200The ping key is deleted.
{
"id": "jmk_6TnBv2QwLsJ",
"deleted": true
}
401The key is missing or incorrect.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The key can't use job monitors.
{
"error": {
"code": "forbidden",
"message": "This key cannot access this API. Update its permissions on the API keys page."
}
}
404The resource wasn't found in your team.
{
"error": {
"code": "not_found",
"message": "The monitor doesn't exist."
}
}