Website Uptime, Status & Redirect Monitor
Pricing
from $0.50 / 1,000 url checkeds
Website Uptime, Status & Redirect Monitor
Check a list of URLs for status code, response time, redirect chain, TLS and a required keyword. Remembers the last state of each URL and flags when a site goes down or comes back up. Built for scheduled runs.
Pricing
from $0.50 / 1,000 url checkeds
Rating
0.0
(0)
Developer
Matias Isnard
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Give it a list of URLs. For each one you get the final status code, the total response time, every redirect on the way (status and location per hop), the final URL, whether TLS was valid, the content length and, if you ask for it, whether a keyword appears on the page. Each URL is marked up, down or degraded.
The Actor remembers the state of every URL between runs. When a URL goes down or comes back up, the result has stateChanged: true and the previous state, so you can schedule it every few minutes and only react to changes.
Each URL gets one GET request, plus one per redirect. Nothing else is fetched: no images, scripts or other pages. Requests identify themselves with the User-Agent Mozilla/5.0 (compatible; ApifyUptimeMonitor/1.0; +https://apify.com/isnard/uptime-monitor).
Input
{"urls": ["http://wikipedia.org", "https://github.com", "https://shop.example.com/health"],"timeoutSecs": 15,"expectedStatus": "200-399","keyword": "","slowThresholdMs": 3000,"maxRedirects": 10}
urls(required): URLs to check.https://is added when there is no scheme. Duplicates are checked once. Invalid lines are skipped with a warning and not charged.timeoutSecs(default 15): limit for each request in the chain.expectedStatus(default200-399): final status codes that count as up. Ranges and single codes separated by commas, for example200-299,301or200,401.keyword(optional): text that must appear in the final response body. Case-insensitive, first 2 MB only. The same keyword is used for all URLs in the run.slowThresholdMs(default 3000): above this total time, a URL that is otherwise fine isdegraded.maxRedirects(default 10): more redirects than this make the URLdown.
How the state is decided
| State | When |
|---|---|
down | DNS, connection, TLS or timeout error; too many redirects; final status not in expectedStatus; keyword not found |
degraded | Not down, but total response time above slowThresholdMs |
up | Everything else |
stateChanged is true only when a URL goes from up or degraded to down, or from down back to up or degraded. Moving between up and degraded keeps stateChanged: false (you still see it in previousState), so a slow minute does not wake anyone up. The first time a URL is checked there is no previous state and stateChanged is false.
downSince is the time of the first check that found the current outage. It stays the same while the URL keeps failing and goes back to null when it recovers. On the recovery result, wasDownSince tells you when that outage started.
States are kept in a named key-value store called uptime-monitor-state in your Apify account (key STATE, one entry per URL). It survives between runs and between schedules. Delete the store to start fresh.
Output
One dataset item per URL. Example of a URL with two redirects (real output from a test run, more in ejemplos/salida.json in the source):
{"url": "http://wikipedia.org","checkedAt": "2026-10-29T09:25:43+00:00","state": "up","previousState": "up","stateChanged": false,"downSince": null,"wasDownSince": null,"reason": null,"statusCode": 200,"finalUrl": "https://www.wikipedia.org/","contentLength": 122421,"keywordFound": null,"tlsOk": true,"error": null,"responseTimeMs": 383,"redirectCount": 2,"redirectChain": [{ "url": "http://wikipedia.org", "status": 301, "location": "https://wikipedia.org/" },{ "url": "https://wikipedia.org/", "status": 301, "location": "https://www.wikipedia.org/" }]}
A URL that just recovered has "previousState": "down", "stateChanged": true, "downSince": null and "wasDownSince" set to the start of the outage.
reason: why the URL is down or degraded, for exampleUnexpected status 404,DNS lookup failed (host not found),TLS certificate not valid: certificate has expired.responseTimeMs: total for all hops, from sending each request until its body is read.tlsOk:truewhen every https hop had a valid certificate,falseon a TLS error,nullwhen no https request got an answer (plain http, or DNS failure).contentLength: theContent-Lengthheader, or the bytes received (up to 2 MB) when the server does not send it.keywordFound:nullwhen no keyword was given.
The run's status message summarizes the run, for example 4 up, 0 degraded, 2 down. Changed: https://shop.example.com/health down.
A URL that cannot be reached still gets a result with state: "down". The Actor does not fail because of a bad URL.
Scheduling every 5 or 15 minutes
- Save your input as a task: open the Actor in Apify Console, fill in the input and click Save as a new task.
- Go to Schedules, click Create new, and set the cron expression:
- every 5 minutes:
*/5 * * * * - every 15 minutes:
*/15 * * * *
- every 5 minutes:
- Add the task to the schedule and save.
Because the state lives in a named store, every scheduled run compares against the previous one automatically. If you run several tasks with different URL lists, they share the store, which is fine as long as the same URL is not checked by two tasks at the same moment.
Getting alerts
Each run is a normal Apify run, so any Apify integration can react to it. On the task, open the Integrations tab:
- Email: add the email integration on "Run succeeded". You get a message after each run with the status message (which lists changed URLs). With a 5-minute schedule that is a lot of email, so this works best with longer intervals.
- Slack: connect the Slack integration and post to a channel on "Run succeeded". Same idea: the status message tells you what changed.
- Webhooks (alerts only on changes): add an HTTP webhook on "Run succeeded" pointing to your own endpoint, or to Make, Zapier or n8n. The payload includes
resource.defaultDatasetId. Fetchhttps://api.apify.com/v2/datasets/<defaultDatasetId>/items?clean=trueand keep the items wherestateChangedistrue. Send the alert only when that list is not empty.
Pricing
Pay per event:
url-checked: each URL checked, including URLs that turn out to be down, because the requests were still made. Invalid input lines are not charged.state-changed: only when a URL goes down or comes back up.
Example: 10 URLs every 15 minutes is about 28,800 checks per month. You can cap the cost of a run with the "Maximum cost per run" setting; the Actor stops cleanly when the cap is reached and saves the state of the URLs it did check.
Limitations
- Checks run from Apify's servers, in one region. A site blocked only in some countries, or a problem with one CDN location, may not show up.
- Each run is a single check per URL with no retry. One timeout is enough to mark a URL down. If that is too sensitive, raise
timeoutSecs. - Only GET. HEAD, POST, custom headers, authentication and cookies are not supported.
- The keyword search reads the raw body. Text added later by JavaScript is not seen.
- Some sites block automated requests and answer 403 or 503 even when they are working. Add those codes to
expectedStatusif that is normal for your site. - Response times include TLS handshake and body download, measured from Apify's network. Compare them with each other, not with a browser.
- The state store keeps entries for URLs you no longer check. They do no harm; delete the store if you want to clean up.
Run locally
python -m venv .venv.venv/Scripts/pip install -r requirements.txt # on Linux/macOS: .venv/bin/pip# put input in storage/key_value_stores/default/INPUT.json.venv/Scripts/python -m my_actorpython -m unittest discover -s tests -t .
Author: Matias Isnard.