Management API
The Management API lets you create and run checks from your own code, your CI pipeline, or an internal tool, instead of clicking through the dashboard. You can configure every part of a check, pause and resume monitoring, and read back its pings and its full status history. Everything the checks dashboard does, the API does too.
The API speaks JSON over HTTPS and is versioned under a stable base URL. Version 1 is the current version, and any breaking change will ship under a new version so your integrations keep working.
https://api.pingark.com/api/v1
Copy
Authentication
Every request is authenticated with a project API key. Create one under API keys in the dashboard. A key belongs to a single project and is shown to you only once at creation, so copy it then and store it somewhere safe. If a key leaks, revoke it and issue a new one.
Send the key in a header. Both of these are accepted, so use whichever fits your client.
curl https://api.pingark.com/api/v1/checks \ -H "Authorization: Bearer pa_your_key_here" curl https://api.pingark.com/api/v1/checks \ -H "X-Api-Key: pa_your_key_here"Copy
Keys carry one of two scopes:
- Read-write keys can do everything: read, create, update, pause, resume, and delete.
- Read-only keys can only read. Any write returns 403 Forbidden. A read-only key is the safe choice for a dashboard, a report, or anything you do not fully trust.
A request with no key, or an unknown key, returns 401 Unauthorized. A key only ever sees its own project, so a check id from another project returns 404, never another project's data.
Requests & responses
Send request bodies as JSON with a Content-Type: application/json header. Responses are JSON too. A single check comes back under a check key, and collections come back under a named key such as checks or pings.
{
"check": { "...": "one check object" }
}
{
"checks": [ { "...": "many" } ]
}
All timestamps are ISO 8601 strings in UTC, for example 2026-06-30T02:00:12+00:00. Durations and periods are whole seconds. A null field means the value is not set, such as a check that has never been pinged.
Rate limits
Each API key may make up to 120 requests per minute. Past that, the API returns 429 Too Many Requests; wait for the window to reset and try again. The limit is per key, so separate keys for separate jobs do not compete with each other.
Status codes
The API uses standard HTTP status codes. Anything in the 2xx range is success, 4xx means the request needs fixing, and 5xx means something went wrong on our side.
| Code | Meaning |
|---|---|
| 200 OK | The request succeeded. |
| 201 Created | A check was created. |
| 204 No Content | A check was deleted. The body is empty. |
| 401 Unauthorized | The API key is missing or invalid. |
| 403 Forbidden | A read-only key tried to make a change. |
| 404 Not Found | No such check, ping, or route in this project. |
| 422 Unprocessable | The body failed validation. The response lists the fields. |
| 429 Too Many Requests | The rate limit was exceeded. |
The check object
A check is the central resource. Most fields are writable on create and update; a handful are read-only and reflect the live state PingArk computes for you. Here is a full example.
{
"id": "b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f",
"name": "Nightly backup",
"description": "Postgres dump to object storage",
"slug": "nightly-backup",
"tags": ["backups", "prod"],
"channels": [12, 15],
"schedule_type": "cron",
"period": null,
"schedule_expr": "0 2 * * *",
"timezone": "UTC",
"grace": 900,
"filter_methods": "any",
"filter_body": false,
"start_kw": null,
"success_kw": null,
"fail_kw": null,
"filter_unmatched": "ignore",
"paused_ping": "resume",
"status": "up",
"last_ping_at": "2026-06-30T02:00:12+00:00",
"last_duration": 42,
"next_expected_at": "2026-07-01T02:00:00+00:00",
"n_pings": 128,
"ping_url": "https://ping.pingark.com/ping/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f",
"slug_url": "https://ping.pingark.com/ping/9f2k1a/nightly-backup",
"created_at": "2026-01-04T09:11:00+00:00",
"updated_at": "2026-06-30T02:00:12+00:00"
}
Writable fields
| Field | Type | Notes |
|---|---|---|
| name | string | Required. Up to 255 characters. |
| description | string, null | Free-text note, up to 2000 characters. Shown on the check and included in email and Slack alerts. |
| slug | string, null | The readable part of the ping URL. Lowercase letters, numbers, and hyphens. Derived from the name if you leave it blank, and made unique within the project. |
| tags | array of strings | Project-scoped labels, up to 50 characters each. Reused across checks rather than duplicated. Sending this replaces the check's tags. |
| channels | array of integers | Channel ids this check alerts through. An empty array means default-on for every channel in the project. Discover ids with List channels. Sending this replaces the set; omitting it leaves it unchanged. |
| schedule_type | string | Required. One of simple, cron, or oncalendar. See Schedule expressions. |
| period | integer, null | Seconds between expected pings, for a simple schedule. From 60 to 31536000 (1 minute to 365 days). Required when schedule_type is simple. |
| schedule_expr | string, null | A cron or systemd OnCalendar expression. Required when schedule_type is cron or oncalendar. |
| timezone | string | Required. An IANA timezone name such as UTC or Europe/London, used to evaluate the schedule. |
| grace | integer | Required. Seconds to wait after a missed deadline before alerting, from 60 to 2592000 (1 minute to 30 days). Set it to the job's normal run time plus headroom. |
| filter_methods | string | any (accept HEAD, GET, POST) or post (POST only). See Filtering rules. |
| filter_body | boolean | Whether to classify a ping by matching keywords in its POST body. |
| start_kw | string, null | Comma-separated keywords that mark a ping as a start. |
| success_kw | string, null | Comma-separated keywords that mark a ping as success. |
| fail_kw | string, null | Comma-separated keywords that mark a ping as a failure. |
| filter_unmatched | string | ignore or fail: what to do with a filtered ping that matches no keyword. |
| paused_ping | string | resume or ignore: what a ping does to a paused check. |
The filtering fields are optional on create and update. If you leave them out, they keep their current values, so a partial update never wipes a check's filtering config. Turning filter_body off clears the keyword fields.
Read-only fields
| Field | Type | Notes |
|---|---|---|
| id | string | The check's UUID. This is also its ping URL, so treat it as a secret. |
| status | string | One of new, up, late, down, paused. See Check states. |
| last_ping_at | string, null | When the last ping arrived. |
| last_duration | integer, null | Seconds between the last start ping and its matching success. |
| next_expected_at | string, null | When the next ping is due. |
| n_pings | integer | Lifetime count of pings received. |
| ping_url | string | The check's ping URL, by UUID. |
| slug_url | string | The readable ping URL, by project ping key and slug. |
| created_at | string | When the check was created. |
| updated_at | string | When the check was last changed. |
List checks
GET /api/v1/checks
Returns every check in the key's project, ordered by name.
curl https://api.pingark.com/api/v1/checks \ -H "Authorization: Bearer pa_your_key_here"Copy
{
"checks": [
{
"id": "b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f",
"name": "Nightly backup",
"status": "up",
"...": "all check fields"
}
]
}
Create a check
POST /api/v1/checks
Creates a check and returns it with 201 Created. A new check is inert: it does not arm or alert until its first ping. Requires a read-write key.
curl https://api.pingark.com/api/v1/checks \ -H "Authorization: Bearer pa_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Nightly backup", "description": "Postgres dump to object storage", "tags": ["backups", "prod"], "schedule_type": "cron", "schedule_expr": "0 2 * * *", "timezone": "UTC", "grace": 900, "channels": [12, 15] }'Copy
{
"check": {
"id": "b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f",
"name": "Nightly backup",
"slug": "nightly-backup",
"status": "new",
"...": "all check fields"
}
}
Get a check
GET /api/v1/checks/{uuid}
Returns one check by its id.
curl https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f \ -H "Authorization: Bearer pa_your_key_here"Copy
Update a check
PUT /api/v1/checks/{uuid}
Updates a check and returns it. The schedule fields, timezone, grace, and name are required, the same as on create. Changing the schedule of a running check re-arms its deadline at once. Requires a read-write key.
curl -X PUT https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f \ -H "Authorization: Bearer pa_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Nightly backup", "schedule_type": "simple", "period": 86400, "timezone": "UTC", "grace": 1800, "filter_methods": "post", "filter_body": true, "fail_kw": "ERROR,Traceback" }'Copy
Omit channels or tags to leave them as they are. Send them to replace the whole set.
Pause a check
POST /api/v1/checks/{uuid}/pause
Pauses monitoring and returns the check. A paused check never alerts. By default its next ping resumes it; set paused_ping to ignore to keep it paused through pings. Requires a read-write key.
curl -X POST https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f/pause \ -H "Authorization: Bearer pa_your_key_here"Copy
Resume a check
POST /api/v1/checks/{uuid}/resume
Resumes a paused check and returns it. The deadline is armed from now, so the job gets a full period plus grace before any alert. Requires a read-write key.
curl -X POST https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f/resume \ -H "Authorization: Bearer pa_your_key_here"Copy
Delete a check
DELETE /api/v1/checks/{uuid}
Deletes a check and all of its history, then returns 204 No Content. This cannot be undone. Requires a read-write key.
curl -X DELETE https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f \ -H "Authorization: Bearer pa_your_key_here"Copy
List pings
GET /api/v1/checks/{uuid}/pings
Returns the check's pings, newest first. Pass ?limit= to cap the count; the default is 100 and the most you can ask for is 1000. The has_body flag tells you whether a ping carried a POST body you can fetch.
curl "https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f/pings?limit=20" \ -H "Authorization: Bearer pa_your_key_here"Copy
{
"pings": [
{
"id": 90817,
"kind": "success",
"exit_status": null,
"duration": 42,
"source_ip": "203.0.113.10",
"has_body": true,
"at": "2026-06-30T02:00:12+00:00"
}
]
}
The kind is one of success, start, fail, exitcode, or log. For an exitcode ping, exit_status holds the reported code.
Get a ping body
GET /api/v1/checks/{uuid}/pings/{id}/body
Returns the raw POST body of a single ping as plain text, using the ping id from the pings list. If the ping carried no body, this returns 404.
curl https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f/pings/90817/body \ -H "Authorization: Bearer pa_your_key_here"Copy
List status changes
GET /api/v1/checks/{uuid}/flips
Returns the check's recorded state changes, newest first, which is the data behind downtime history. Pass ?limit= to cap the count, the same as for pings.
curl https://api.pingark.com/api/v1/checks/b3f1c2a8-1d4e-4c6a-9f2b-7e8a1c2d3e4f/flips \ -H "Authorization: Bearer pa_your_key_here"Copy
{
"flips": [
{
"from": "up",
"to": "down",
"reason": "deadline",
"at": "2026-06-29T02:15:00+00:00"
}
]
}
The reason is one of deadline, fail-ping, recovery, manual, first-ping, or resume.
List channels
GET /api/v1/channels
Returns the project's notification channels. Use it to find the channel ids you attach to a check. The kind is one of email, slack, discord, telegram, webhook, pagerduty, or the metered premium kinds whatsapp, voice, and sms.
curl https://api.pingark.com/api/v1/channels \ -H "Authorization: Bearer pa_your_key_here"Copy
{
"channels": [
{
"id": 12,
"kind": "slack",
"label": "hooks.slack.com/services…",
"enabled": true,
"verified": true
},
{
"id": 13,
"kind": "whatsapp",
"label": "+15551234567",
"enabled": true,
"verified": false
}
]
}
Email and the phone channels (WhatsApp, voice, SMS) only deliver once they are verified, so check the verified flag before you rely on one. Channels are created and verified in the dashboard, not over the API; premium channels are also metered by credits.
Schedule expressions
A check expects its next ping on a schedule. There are three kinds, set with schedule_type.
- simple. A fixed period in seconds, in period. For example 3600 means every hour.
- cron. A standard five-field cron expression in schedule_expr, evaluated in timezone. For example 0 2 * * * is every day at 02:00.
- oncalendar. A systemd OnCalendar expression in schedule_expr, evaluated in timezone, for schedules cron cannot express.
Whichever you use, set grace to cover the job's normal run time plus a little headroom so a slow run does not trip a false alarm.
Filtering rules
Filtering rules tune how a check reads its pings, for noisier setups.
- Accepted methods. filter_methods accepts any (HEAD, GET, POST) or post (POST only), so a link-preview scanner's GET cannot file a false ping.
- Content filtering. With filter_body on, PingArk matches case-sensitive keywords in the POST body to classify a ping. Set start_kw, success_kw, and fail_kw as comma-separated lists. This helps when a job always exits zero but prints "ERROR". filter_unmatched decides what an unmatched ping does: ignore it or treat it as a fail.
- Paused pings. paused_ping decides whether a ping to a paused check resumes it or is ignored.
Check states
| Status | Meaning |
|---|---|
| new | Created but never pinged. Inert: it does not alert until its first ping. |
| up | Healthy. The last ping arrived on time. |
| late | The deadline has passed but the grace window has not. No alert yet. This status is computed on read, never stored. |
| down | The grace window passed with no ping, or the job reported a failure. This is when PingArk alerts. |
| paused | Monitoring is paused. It never alerts until resumed. |