Try the RankNibbler SEO API

Results Beta

GET /api/v1/monitors/{id}/results

Every check a monitor has run, newest first: whether it was up, the HTTP status, the response time, any error, and where it was checked from.

Needs an X-API-Key header whose owner has access to uptime monitoring in the app. Each call, each page included, counts once against your daily quota. See Monitoring API: getting started for authentication, rate limits and errors.

Path parameters

NameTypeDescription
id requiredstringThe monitor id, for example mon_79ce3cf9c4fa56e98af4.

Query parameters

NameTypeDescription
from optionalstring | integerStart of the window, as ISO 8601 or UNIX seconds. Default: 24 hours before to.
to optionalstring | integerEnd of the window, as ISO 8601 or UNIX seconds. Default: now.
status optionalstringup or down. Leave out for both.
region optionalstringComma list of region codes: default (United Kingdom), na (North America), eu (Europe). Unknown codes are ignored.
minMs optionalintegerOnly checks that took at least this many milliseconds.
maxMs optionalintegerOnly checks that took at most this many milliseconds.
page optionalintegerPage number, starting at 1. Default 1.
per optionalintegerResults per page, 1 to 1000. Default 100.

Request

Response

200 · application/json
  1. {
  2. "id": "mon_79ce3cf9c4fa56e98af4",
  3. "from": "2026-09-10T00:00:00.000Z",
  4. "to": "2026-09-17T00:00:00.000Z",
  5. "total": 3,
  6. "page": 1,
  7. "per": 2,
  8. "results": [
  9. {
  10. "checkedAt": "2026-09-16T22:40:04.883Z",
  11. "status": "down",
  12. "statusCode": 502,
  13. "responseMs": 611,
  14. "error": "HTTP 502",
  15. "region": "default"
  16. },
  17. {
  18. "checkedAt": "2026-09-16T22:27:10.114Z",
  19. "status": "down",
  20. "statusCode": null,
  21. "responseMs": null,
  22. "error": "Connection refused",
  23. "region": "eu"
  24. }
  25. ],
  26. "usage": { "used": 21, "dailyLimit": 100, "tier": "free" }
  27. }

Response fields

FieldTypeDescription
idstringThe monitor id.
from, tostringThe window that was used, in ISO 8601 UTC. Useful when you relied on the defaults.
totalintegerHow many checks match, across all pages.
page, perintegerThe page returned and the page size.
results[].checkedAtstringWhen the check ran.
results[].statusstringup or down.
results[].statusCodeinteger | nullHTTP status, or null when no response came back.
results[].responseMsinteger | nullResponse time in milliseconds.
results[].errorstring | nullWhy the check failed, such as "Connection refused", "SSL/TLS error" or a missing expected text. null when it passed.
results[].regionstringWhere the check ran from: default, na or eu.

Results are sorted newest first. A scheduled check is only stored as down after its retries also failed, so each down result is a confirmed failure.

Errors

StatusWhen
400from or to isn't a valid date, from is after to, the window is longer than 92 days, or status isn't up or down.
401Missing or invalid API key.
403The key's owner doesn't have access to uptime monitoring, or the key isn't linked to a workspace.
404No monitor with that id in your workspace.
405A method other than GET.
429A rate limit or your daily quota was hit. Read Retry-After.

Paging through a longer period

To read everything in a period, fetch pages until you have total results. For more than 92 days, split the period into windows of 92 days or less and page through each one. Use per=1000 to keep the number of requests, and so your quota use, down: a monitor checked every 5 minutes runs about 8,640 checks in 30 days, which is 9 requests.

This script uses curl and jq to write 30 days of results to a file, one JSON object per line:

fetch-results.sh
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. ID="mon_79ce3cf9c4fa56e98af4"
  4. FROM="2026-08-18T00:00:00Z"
  5. TO="2026-09-17T00:00:00Z"
  6. PER=1000
  7. page=1
  8. : > results.jsonl
  9. while :; do
  10. resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  11. "https://www.ranknibbler.com/api/v1/monitors/$ID/results?from=$FROM&to=$TO&per=$PER&page=$page")
  12. printf '%s' "$resp" | jq -c '.results[]' >> results.jsonl
  13. total=$(printf '%s' "$resp" | jq '.total')
  14. echo "page $page done ($(wc -l < results.jsonl) of $total)"
  15. [ $((page * PER)) -ge "$total" ] && break
  16. page=$((page + 1))
  17. sleep 1 # stay well inside the burst limit
  18. done

Keep from and to fixed while you page. If you leave to out, it means "now" on every request, so new checks would shift the pages under you.

The same in Node.js, over several 92-day windows:

fetch-results.mjs
  1. const KEY = process.env.RANKNIBBLER_API_KEY;
  2. const ID = "mon_79ce3cf9c4fa56e98af4";
  3. const DAY = 86400 * 1000;
  4. async function* results(from, to) {
  5. for (let start = from; start < to; start = new Date(start.getTime() + 92 * DAY)) {
  6. const end = new Date(Math.min(start.getTime() + 92 * DAY, to.getTime()));
  7. for (let page = 1; ; page++) {
  8. const url = `https://www.ranknibbler.com/api/v1/monitors/${ID}/results` +
  9. `?from=${start.toISOString()}&to=${end.toISOString()}&per=1000&page=${page}`;
  10. const res = await fetch(url, { headers: { "X-API-Key": KEY } });
  11. if (res.status === 429) {
  12. await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") || 10) * 1000));
  13. page--; continue;
  14. }
  15. if (!res.ok) throw new Error((await res.json()).error);
  16. const body = await res.json();
  17. yield* body.results;
  18. if (page * body.per >= body.total) break;
  19. }
  20. }
  21. }
  22. let down = 0;
  23. for await (const r of results(new Date("2026-03-01T00:00:00Z"), new Date("2026-09-01T00:00:00Z"))) {
  24. if (r.status === "down") down++;
  25. }
  26. console.log(`${down} failed checks`);

Windows that share an edge both include a check that lands exactly on it, so you may see that one check twice. Drop duplicates by checkedAt and region if it matters.

A daily-quota 429 sets Retry-After to the seconds until midnight UTC, so for a very long backfill, check the usage object or X-RateLimit-Remaining and stop before you run out.

Related