Check HTTP status codes and redirect chains for thousands of URLs: every hop with status and Location, final URL, response time, broken flag, and warnings for redirect loops, long chains, temporary redirects and HTTPS to HTTP downgrades. Comprobar redirecciones, verificar redirecionamentos.
Unreleased: invalid input no longer crashes (2026-11-04, local, not built or pushed; based on cloud build 1.0.9)
Fix: input that is not a JSON object (a string, number, null or list) crashed the run with exit 91. It now ends with exit 0 and one free status row: Invalid input, nothing was fetched and no results were charged: the input must be a JSON object, got <type>.
Fix: wrong-type or unusable values that crashed with exit 91 before anything was fetched now end the same way (exit 0, one free status row with charged: false, no RUN_SUMMARY, no charged event) with the reason <field> has the wrong type (<error>). Covered: urls (a list with numbers or other non-text entries, or a number), maxRedirects, maxResults, maxConcurrency.
The check repeats the exact conversions the run makes, before any request; valid input behaves exactly as before. Network, HTTP and upstream errors are unchanged.
The deliberate free rows (demo row, rows for URLs that could not be checked, robots.txt rows) are unchanged.
Memory: defaultMemoryMbytes is now 256 (was 1024); min 128 and max 2048 unchanged. README: the start fee is now described at the default 256 MB (was 1 GB) in the At a glance line, the pricing bullet and the examples heading; amounts are unchanged (1 start event).
Correction to the 2026-11-02 entry below (build 1.0.6): the README sentence on blocked URLs is no longer in the
Output section. Build 1.0.7 moved it to a new "Limits" section. No code or README change.
2026-11-02 (actor.json version unchanged: 1.0)
Added: private-address (SSRF) guard
On the Apify platform, every request (the URL itself, each redirect hop, and robots.txt) is refused when the host
resolves to a private, loopback, link-local, carrier-grade NAT, reserved, multicast or unspecified address
(including IPv4-mapped IPv6), or when any of the host's addresses is one. Same rules as Bulk HTTP Request.
Such a URL gets a free error row: error: "private, loopback and link-local addresses are not allowed",
category: "error", isBroken: true, status: "error" and a message starting "Not charged". A redirect hop
to a private address stops the chain there; the hops before it stay in redirectChain.
A host the guard cannot resolve fails as a DNS error, as before (error now reads
"DNS or connection error: DNS lookup failed for "); the row stays free.
A robots.txt that redirects to a private address counts as unavailable (no robots.txt rules), like a robots.txt
that cannot be fetched.
Local runs (APIFY_IS_AT_HOME not set) are unchanged.
Files changed
my_actor/common.py: ip_blocked, host_problem, check_public, guard_request (httpx request hook),
guard_site, Blocked, DnsLookupFailed; Http._robots_for checks the site before fetching robots.txt.
my_actor/main.py: the httpx client gets the request hook; Blocked becomes a free error row.
tests/test_ssrf_guard_1102t.py: new. README: one sentence on blocked URLs in the Output section.
2026-10-30, 10:17 PM EDT (actor.json version unchanged: 1.0), docs only
README.md (publisher, from Echo's README.proposed.md checked against the code): error rows (connection/DNS
errors, timeouts, redirect loops, too many redirects), robots.txt-skipped rows and the {} / example.com demo row
are free and carry status and message; charged rows have neither field. Free rows waive only the per-URL fee:
the apify-actor-start event ($0.00005 per GB of memory) is still charged on every run, so {} costs $0.00005 at
the default 1 GB. Cost examples updated; RUN_SUMMARYrowsCharged / rowsFree documented. This also corrects
the entry below, which says only that {} "succeeds with one free row".
2026-10-30 (actor.json version unchanged: 1.0)
Changed: rows that are not a real check result are free
These dataset rows are now pushed without the url event (not charged):
Error rows: every row with category: "error": DNS or connection errors, timeouts, other request exceptions,
redirect loop and more than N redirects.
The demo check of the input example: when the URL list is empty, or is only https://example.com (the input
schema default and prefill, so also what the platform sends for {}; http://, www. and a trailing / count
too). {} now succeeds with one free row.
Free rows get two extra fields: status (error, skipped or warning) and message (starts with "Not charged").
Charged rows (2xx/3xx/4xx/5xx results) have exactly the same fields as before. Free rows still count towards
maxResults, and never trigger the max-cost stop. RUN_SUMMARY gains rowsCharged and rowsFree;
byCategory now counts only rows that were saved. The status message says how many rows were free.
Files changed
my_actor/common.py: Emitter.push(item, charge=True); charge=False pushes without an event; charged /
free counters.
my_actor/main.py: ROBOTS_ERROR, is_sample(), free_status(); free rows get status/message; run summary
and status message.
tests/test_free_rows.py (new): unit tests and end-to-end runs against a local HTTP server.
pytest.ini (new): test configuration.
CHANGELOG.md (new): this file.
README.md is NOT changed by this entry. .actor/dataset_schema.json view unchanged (status/message are not
shown in the table view).