Ping Happens API
Authentication
Every endpoint below (other than logging in) needs a Bearer token belonging to a client account. There is no separate "API key" and no way to create an account through the API — sign up with Create account on the site first. Once you have an account:
POSTthe email and password for that account to/api/client-authto retrieve a token.- Send that token on every request that follows, as an
Authorizationheader:Authorization: Bearer <token>.
- The token expires 24 hours after it's issued — log in again to get a new one.
- The token is bound to the exact
User-Agentheader your client sent when it was issued, and every later request must send that same value. This isn't something you need to configure — a plaincurlcall already sends a consistent defaultUser-Agenton every request, so it just works. It only matters if you later copy the token into a different tool (a browser, a different HTTP library): that tool'sUser-Agentwon't match, and the token will be rejected. Log in again from whichever tool you intend to keep using.
Log in
Retrieve a Bearer token for an existing account
Auth: None
| Field | Type |
|---|---|
user | string |
password | string |
curl -X POST https://pinghappens.com/api/client-auth \
-H "Content-Type: application/json" \
-d '{
"user": "you@example.com",
"password": "Str0ngPassw0rd!"
}'{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NzAxZjM5YWM0YTFiMmMzZDRlNWY1ZmYiLCJ1c2VyIjoieW91QGV4YW1wbGUuY29tIn0.xH3...",
"client": {
"id": "6701f39ac4a1b2c3d4e5f5ff",
"user": "you@example.com"
}
}{ "error": "Invalid credentials" }{ "error": "Too many failed attempts. Try again in 58 minutes." }3 failed attempts in a row locks the account for 1 hour; a Retry-After header (in seconds) comes back alongside the 429.
token from the response and send it as Authorization: Bearer <token> — for example, as an environment variable:export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Ownership & visibility
- You can always read and write your own services and rules.
- You can never read or write another client's data through the API, even data belonging to a service marked
public— public services are for the public status page on the website, not for anonymous or third-party API access. Fetch them from the site itself, or authenticate as the owner. - Administrators (staff accounts with the
adminrole) can read and write any client's data.
Client profile & services
Get a client's profile and services
Auth: Bearer token, must belong to this client
Returns the client's profile and the list of their services, with a check-status summary for each. Only the account owner or an admin receives every service and the account email; anyone else gets neither.
curl https://pinghappens.com/api/client-services/6701f39ac4a1b2c3d4e5f5ff \
-H "Authorization: Bearer $TOKEN"{
"client": {
"_id": "6701f39ac4a1b2c3d4e5f5ff",
"user": "you@example.com",
"name": "Ada Lovelace"
},
"services": [
{
"_id": "6701f3a1c4a1b2c3d4e5f600",
"resource": "Payments API",
"user": "you@example.com",
"ruleCount": 1,
"public": false,
"status": "healthy",
"lastCheckedAt": "2026-10-03T12:05:00.000Z"
}
],
"authenticated": true
}Services
Create a service
Auth: Bearer token, must match the given userId
| Field | Type | Notes |
|---|---|---|
userId | string | Your client ID |
user | string | Your account email |
resource | string | Service display name |
public | boolean | Defaults to false. See Ownership & visibility |
rule | array | One or more rule objects (can be empty) |
curl -X POST https://pinghappens.com/api/services \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "6701f39ac4a1b2c3d4e5f5ff",
"user": "you@example.com",
"resource": "Payments API",
"public": false,
"rule": [
{
"url": "https://payments.example.com/healthz",
"method": "GET",
"cron": "*/5 * * * *"
}
]
}'{
"service": {
"_id": "6701f3a1c4a1b2c3d4e5f600",
"userId": "6701f39ac4a1b2c3d4e5f5ff",
"user": "you@example.com",
"resource": "Payments API",
"public": false,
"active": true,
"rule": [
{
"id": "3f1b1e2a-1c2d-4e3f-9a0b-123456789abc",
"url": "https://payments.example.com/healthz",
"method": "GET",
"cron": "*/5 * * * *",
"active": true,
"attemptsByAccess": 1,
"attemptsDelay": 1,
"headers": []
}
]
}
}Keep that rule's id — you'll need it for the rule endpoints below.
Get a service
Auth: Owner, admin, or public — see note
Returns the service, its rules, and up to 300 recent check results per rule. A rule's configuration (headers, payload, action) is only included for the owner or an admin. Each check result omits its response body — fetch that on demand with List a service's history.
curl https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600 \
-H "Authorization: Bearer $TOKEN"{
"service": {
"_id": "6701f3a1c4a1b2c3d4e5f600",
"userId": "6701f39ac4a1b2c3d4e5f5ff",
"resource": "Payments API",
"user": "you@example.com",
"public": false,
"active": true,
"rules": [
{
"id": "3f1b1e2a-1c2d-4e3f-9a0b-123456789abc",
"url": "https://payments.example.com/healthz",
"method": "GET",
"attemptsByAccess": 1,
"attemptsDelay": 1,
"cron": "*/5 * * * *",
"active": true,
"hasAuth": false,
"headers": [],
"payload": null,
"action": null,
"history": [
{
"_id": "6701f402c4a1b2c3d4e5f610",
"statusCode": 200,
"responseTime": 184,
"createdAt": "2026-10-03T12:05:00.000Z",
"actionStatusCode": null
}
]
}
]
}
}Rename a service
Auth: Owner or admin
| Field | Type |
|---|---|
resource | string |
curl -X PATCH https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "resource": "Payments API (EU)" }'{
"service": {
"_id": "6701f3a1c4a1b2c3d4e5f600",
"resource": "Payments API (EU)"
}
}Delete a service
Auth: Owner or admin
Also deletes the service's check history. This cannot be undone.
curl -X DELETE https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600 \
-H "Authorization: Bearer $TOKEN"{ "success": true }Rules
Add a rule to a service
Auth: Owner or admin
Body: a rule object.
curl -X POST https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600/rules \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://payments.example.com/status",
"method": "GET",
"cron": "*/15 * * * *",
"attemptsByAccess": 2,
"attemptsDelay": 500
}'{
"rule": {
"id": "9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d",
"url": "https://payments.example.com/status",
"method": "GET",
"attemptsByAccess": 2,
"attemptsDelay": 500,
"cron": "*/15 * * * *",
"active": true,
"hasAuth": false,
"headers": [],
"payload": null,
"action": null
}
}Update a rule
Auth: Owner or admin
Body: any subset of a rule object's fields.
curl -X PATCH https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600/rules/9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "active": false }'{
"rule": {
"id": "9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d",
"url": "https://payments.example.com/status",
"method": "GET",
"attemptsByAccess": 2,
"attemptsDelay": 500,
"cron": "*/15 * * * *",
"active": false,
"hasAuth": false,
"headers": [],
"payload": null,
"action": null
}
}Delete a rule
Auth: Owner or admin
Removes the rule. Its past check history is kept with the service.
curl -X DELETE https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600/rules/9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d \
-H "Authorization: Bearer $TOKEN"{ "success": true }History
List a service's check history
Auth: Owner or admin
Returns the service's check results newest first, including each check's response body (truncated to 2000 characters), which Get a service omits, and the ruleUrl of the rule that produced it (null if that rule has since been deleted). Results are paginated at up to 100 entries per page, and only checks from the last 6 months are ever returned — older entries are excluded regardless of the filters used. Only the service's owner (or an admin) can call this endpoint, even when the service is public; a service or rule belonging to another account responds exactly like one that doesn't exist.
| Query parameter | Type | Notes |
|---|---|---|
page | number | Page number, starting at 1. Defaults to 1 |
limit | number | Entries per page, 1–100. Defaults to 100 |
ruleId | string | Optional — only entries for this rule's id |
historyId | string | Optional — only the entry with this _id, e.g. from a history entry in Get a service |
curl "https://pinghappens.com/api/services/6701f3a1c4a1b2c3d4e5f600/history?page=1&ruleId=9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d" \
-H "Authorization: Bearer $TOKEN"{
"history": [
{
"_id": "6701f402c4a1b2c3d4e5f610",
"ruleId": "9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d",
"ruleUrl": "https://payments.example.com/status",
"statusCode": 200,
"responseTime": 184,
"response": "{\"status\":\"ok\"}",
"actionStatusCode": null,
"createdAt": "2026-10-03T12:05:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 100,
"total": 1,
"totalPages": 1,
"hasMore": false
},
"since": "2026-04-05T12:05:00.000Z"
}since is the oldest timestamp the response can include (6 months before the request). Request the next page while pagination.hasMore is true.
{ "error": "limit must be an integer between 1 and 100" }{ "error": "Unauthorized" }{ "error": "Rule not found" }401 means no valid Bearer token was sent. 404 means the service — or the rule given in ruleId — doesn't exist on your account ("Service not found" / "Rule not found"). A historyId that matches nothing returns 200 with an empty history array.
Failure calendar
List failed checks by day
Auth: Client Bearer token only (not admin)
Returns the failed checks (status code 400 or higher) of your own services, with one entry per service, rule and day. Each entry is the day's last failure and carries failCount, the number of failures that rule had that day. Days are grouped in the time zone you pass in tz. The account is always taken from your token: there is no client id parameter, and admin tokens are not accepted. Failures of rules that have since been deleted are not included. Response bodies are never returned here; use List a service's history for those.
| Query parameter | Type | Notes |
|---|---|---|
start | string | ISO 8601 date/time, inclusive. Required |
end | string | ISO 8601 date/time, exclusive. Required. At most 32 days after start |
tz | string | IANA time zone used to group days, e.g. America/Sao_Paulo. Required |
curl "https://pinghappens.com/api/client-calendar?start=2026-10-01T03:00:00.000Z&end=2026-11-01T03:00:00.000Z&tz=America/Sao_Paulo" \
-H "Authorization: Bearer $TOKEN"{
"events": [
{
"id": "6701f3a1c4a1b2c3d4e5f600:9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d:2026-10-03",
"serviceId": "6701f3a1c4a1b2c3d4e5f600",
"serviceName": "payments-api",
"ruleId": "9c2b6a4e-5d3f-4a1b-8c7d-0e1f2a3b4c5d",
"method": "GET",
"url": "https://payments.example.com/status",
"statusCode": 503,
"failCount": 3,
"day": "2026-10-03",
"lastFailAt": "2026-10-03T21:40:00.000Z"
}
]
}{ "error": "end must be after start, within 32 days" }{ "error": "Unauthorized" }Export failures as CSV
Auth: Client Bearer token only (not admin)
Downloads every failed check (status code 400 or higher) of your own services in the range as a CSV file, one line per failure, oldest first. It takes the same start, end and tz parameters as /api/client-calendar above. The columns are Service, Rule URL, Error Status, Date (YYYY-MM-DD) and Time (HH:mm:ss, 24h), with Date and Time in tz. The file is UTF-8 with a BOM, and failures of deleted rules are not included.
Limited to one export per hour per account, whatever the month. Requests rejected with 400 don't count towards the limit. Within the hour, the response is 429 with a Retry-After header (in seconds).
curl "https://pinghappens.com/api/client-calendar/export?start=2026-10-01T03:00:00.000Z&end=2026-11-01T03:00:00.000Z&tz=America/Sao_Paulo" \
-H "Authorization: Bearer $TOKEN" \
-o failures.csv"Service","Rule URL","Error Status","Date","Time"
"payments-api","https://payments.example.com/status","503","2026-10-03","18:35:00"
"payments-api","https://payments.example.com/status","503","2026-10-03","18:40:00"{ "error": "Only one export per hour. Try again in 42 minutes." }Rule object
| Field | Type | Notes |
|---|---|---|
url | string | Target URL to check, required |
method | string | GET | POST | PUT | DELETE |
cron | string | Cron expression, defaults to every 5 minutes |
active | boolean | Defaults to true |
attemptsByAccess | number | Retries per check, 1–3 |
attemptsDelay | number | Delay between retries in ms, 1–5000 |
auth | string | Optional bearer token sent to the target. Write-only — never returned; omit to leave an existing one unchanged |
headers | array of { name, value } | Extra request headers |
payload | object | JSON request body, for non-GET methods |
action | object or null | Optional webhook, fired when a check's status code matches. Shape: { url, statusCode: number[], headers?, auth? } |
In responses, a rule's auth and action.auth are never echoed back — look for the boolean hasAuth (and action.hasAuth) instead to tell whether one is set.
Errors
| Status | Meaning |
|---|---|
| 400 | The request body failed validation. The response body includes an error message. |
| 401 | Missing, invalid, or insufficient credentials for this resource. |
| 404 | Not found — also returned instead of 401 for a private resource you don't own, so its existence isn't revealed. |
| 429 | Too many failed login attempts — the account is temporarily locked. See Log in. Also returned by the CSV export within an hour of the previous export. A Retry-After header is included. |
| 500 | Unexpected server error. |