Feed Quality Monitor: RSS, Atom & JSON QA avatar

Feed Quality Monitor: RSS, Atom & JSON QA

Pricing

from $10.00 / 1,000 feed auditeds

Go to Apify Store
Feed Quality Monitor: RSS, Atom & JSON QA

Feed Quality Monitor: RSS, Atom & JSON QA

Audit up to 20 public RSS, Atom, RDF/RSS, or JSON Feed endpoints. Detect broken delivery, malformed feeds, stale content, missing fields, duplicates, invalid URLs and dates, and entry-limit truncation; export normalized entries plus reusable ETag and Last-Modified state.

Pricing

from $10.00 / 1,000 feed auditeds

Rating

0.0

(0)

Developer

Sassy Labs

Sassy Labs

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

Feed Quality Monitor

Feed Quality Monitor is a deterministic RSS feed validator and parser API for RSS, Atom, RDF/RSS, and JSON Feed production pipelines. Add 1–20 public feed URLs and get explicit pass, fail, or unknown health receipts for delivery, parsing, required fields, dates, freshness, duplicates, and entry limits—plus normalized entries and reusable ETag/Last-Modified state.

Use it to catch a feed that is reachable but stale, structurally broken, missing required data, duplicating items, or returning inconclusive evidence. It reads only the public feed endpoint: it does not crawl linked articles, discover feeds from websites, bypass access controls, or ask a language model to guess missing values. Schedule repeated health checks when useful, but note that v0.2 returns cache validators rather than retaining prior entries or claiming stateful change detection.

Live Store listing: https://apify.com/sassy_labs/feed-quality-monitor

What v0.2 checks

For each feed:

  • public-only HTTP delivery, final status, and redirect count;
  • redirect destination revalidation before every request;
  • recognized feed media types, while still allowing a valid feed served under a generic type;
  • well-formed XML or JSON and a supported RSS, Atom, RDF/RSS, or JSON Feed structure;
  • required feed metadata;
  • invalid metadata URLs and dates;
  • a bounded maximum entry count;
  • duplicate entries by stable source ID, normalized URL, then deterministic content hash;
  • required entry fields;
  • invalid entry URLs and dates;
  • newest declared feed or entry date against staleAfterHours; and
  • ETag, Last-Modified, conditional-request, and response-cache behavior.

Long summary and content values are represented by bounded previews. Any preview truncation is named in truncatedFields and in a non-blocking output_truncation receipt. Entries beyond maxEntriesPerFeed are not hidden: the feed fails entry_limit and reports the exact omitted count.

Example input

{
"feeds": [
{
"url": "https://www.w3.org/blog/news/feed/"
},
{
"url": "https://example.com/feed.json",
"etag": "W/\"prior-etag\"",
"lastModified": "Wed, 15 Jul 2026 12:00:00 GMT"
}
],
"timeoutSeconds": 20,
"maxRedirects": 5,
"maxFeedBytes": 5000000,
"maxEntriesPerFeed": 500,
"staleAfterHours": 168,
"concurrency": 4,
"requiredFeedFields": [
"title"
],
"requiredEntryFields": [
"title",
"url",
"date"
]
}

One to twenty feed targets are accepted. Duplicate target URLs and unsupported input fields are rejected rather than merged silently.

Current pay-per-feed billing

The live Store listing uses pure pay-per-event pricing:

  • apify-actor-start: the platform-managed synthetic start event at $0.00005; Actor code never charges it;
  • feed-audited: one custom $0.01 event charged after complete input validation and immediately before each feed request; and
  • apify-default-dataset-item: disabled, because one feed can produce hundreds of diagnostic records without creating hundreds of units of buyer value.

Platform usage is included in the event price (platformUsagePassedToUser=false). Event charges are therefore $0.01005 for one feed, $0.05005 for five feeds, and $0.20005 for twenty feeds.

Every ChargeResult is authoritative. If chargedCount is zero or the event budget is exhausted, that feed is not requested. It still receives one budget_limit feed record with status: "unknown" and audited: false. The overview separately reports requestedFeeds, processedFeeds, and budgetLimitedFeeds. Paid feed requests can run concurrently, but charge gates are serialized so a tight ACTOR_MAX_TOTAL_CHARGE_USD cannot authorize two feeds at once.

The public configuration was verified on 17 July 2026 with feed-audited as the primary event, platform usage included, limited permissions, hidden source files, Standby disabled, 512 MB minimum/default memory, and a 1024 MB maximum. Owner cloud preflights for 1, 5, and 20 feeds reported total platform usage of approximately USD 0.00034, USD 0.00093, and USD 0.00294. Those runs are cost and reliability evidence, not customer demand or revenue.

Conditional requests

After a run, the default key-value store record CACHE_STATE contains final feed URLs and any returned ETag or Last-Modified values:

[
{
"url": "https://www.w3.org/news/feed/",
"etag": "\"example-etag\"",
"lastModified": null
}
]

Use those objects as the next run's feeds value. A validator is sent only on the first request to that exact URL. It is deliberately dropped after a redirect so an opaque ETag or Last-Modified value cannot leak to another origin. Because the returned state uses the final URL, later runs can normally avoid the redirect and make a useful conditional request directly.

An HTTP 304 proves that the conditional transport path worked, but it does not provide a body. v0.2 therefore marks parsing and freshness unknown instead of reconstructing or inventing prior content. The returned validators are preserved for the next run.

Output contract

Dataset records are ordered:

  1. exactly one overview;
  2. one feed record for each requested target, in input order; and
  3. that feed's unique entry records, in source order.

The overview dataset view is optimized for record type, status, format, summary, URLs, HTTP status, dedupe method, duplicate count, and check time. Start with the overview, then scan each feed and normalized entry. CACHE_STATE separately returns reusable ETag and Last-Modified validators.

Each feed and entry record contains a receipts array. Every receipt names:

  • a stable check ID;
  • pass, fail, or unknown;
  • severity;
  • whether it affects the parent record's status;
  • expected behavior;
  • observed evidence; and
  • a small evidence array.

Normalized entry output includes source ID, title, absolute fragment-free HTTP URL, external URL, bounded summary and content preview, SHA-256 content hash, published and modified dates, raw date strings, authors, tags, dedupe method, dedupe key, and duplicate occurrences.

No missing title, URL, date, author, tag, or feed field is synthesized. Relative HTTP links are resolved deterministically against the final feed URL; invalid or unsupported links remain null with a failing receipt.

Concrete example output

This abridged output came from the verified 17 July 2026 normalize-rss-atom-json-feed-structured-entries example. It read one RSS 2.0 feed, one Atom feed, and one JSON Feed, then normalized 83 unique entries.

{
"recordType": "overview",
"status": "pass",
"summary": "Feed quality passed for 3 feed(s)",
"totals": {
"requestedFeeds": 3,
"processedFeeds": 3,
"budgetLimitedFeeds": 0,
"entries": 83,
"duplicatesSuppressed": 0,
"feedPass": 3,
"feedFail": 0,
"feedUnknown": 0
},
"feedStatuses": [
{
"status": "pass",
"format": "rss2"
},
{
"status": "pass",
"format": "atom"
},
{
"status": "pass",
"format": "json_feed"
}
]
}

The same run returned one reusable cache-state object per feed:

[
{
"url": "https://www.w3.org/news/feed/",
"etag": "\"5cb9491929bf42153cf24adfaba8c052-gzip\"",
"lastModified": null
}
]

Response dates, ETags, entry counts, and content naturally change as publishers update their feeds; this example records the observed run rather than promising fixed values.

Safety boundary

  • Only public HTTP(S) URLs on ports 80 and 443 are accepted.
  • URL credentials, localhost, internal hostnames, and private, reserved, link-local, multicast, and otherwise non-public IP addresses are rejected.
  • Every DNS answer must be public; mixed public/private answers are rejected.
  • The network socket performs its own public-address check, limiting DNS rebinding between validation and connection.
  • Every redirect destination is revalidated, and conditional validators are dropped after the initial URL.
  • Response bodies are streamed through a hard decoded-byte limit.
  • XML DOCTYPE and custom entity declarations are rejected. External entities are never resolved.
  • Runs are bounded to twenty feeds, eight concurrent requests, ten redirects, ten megabytes per feed, and five thousand entries per feed.
  • No login, cookie, API key, proxy subscription, customer credential, scraping bypass, browser automation, or model call is accepted or required.

What it does not claim

This is a deterministic feed-health and normalization check, not a full conformance certification for every RSS extension, a semantic fact checker, an HTML sanitizer, a malware scanner, or proof that subscribers consumed an entry. It does not fetch entry pages, discover feeds from websites, follow pagination hubs, preserve an internal content cache, or mutate a target.

Namespaced RSS fields commonly used for content, creators, dates, and Atom self-links are recognized. Unsupported extension fields remain absent rather than being guessed.

Local verification

Prerequisites: Node.js 24.11+.

npm ci
npm run verify
npx --yes apify-cli@1.7.1 validate-schema
npm audit --omit=dev

The tests inject network fixtures and cover:

  • RSS, Atom, and JSON Feed parsing;
  • malformed XML and JSON;
  • XML custom-entity rejection;
  • private, mixed-DNS, credential, port, and redirect blocking;
  • conditional-header isolation across redirects;
  • decoded response-size limits;
  • deterministic dedupe;
  • required-field, invalid-date, invalid-URL, stale-feed, empty-body, and entry limit behavior;
  • HTTP 304 unknown semantics;
  • ordered dataset and cache-state writes;
  • zero-charge, exhausted-budget, and concurrent one-event PPE gates; and
  • release, Docker, memory, pricing-plan, and dataset-view metadata.

Local pay-per-event boundary check

Apify's local PPE test mode assigns USD 1 to a custom event that is not yet configured in Console. A USD 1 cap therefore permits exactly one feed-audited event. The second feed must remain unrequested and appear as an explicit budget-limited record:

$env:ACTOR_TEST_PAY_PER_EVENT = "true"
$env:ACTOR_USE_CHARGING_LOG_DATASET = "true"
$env:ACTOR_MAX_TOTAL_CHARGE_USD = "1"
npx --yes apify-cli@1.7.1 run --purge --input-file demo/ppe-input.json
npm run verify-ppe-log
Remove-Item Env:ACTOR_TEST_PAY_PER_EVENT
Remove-Item Env:ACTOR_USE_CHARGING_LOG_DATASET
Remove-Item Env:ACTOR_MAX_TOTAL_CHARGE_USD

The verifier requires exactly one charging-log record named feed-audited, one processed feed, one unaudited budget_limit feed, and no manually charged start or dataset-item event.

Live demo

npm run demo

The demo performs a read-only request to the public W3C News RSS feed at https://www.w3.org/blog/news/feed/, writes ignored local artifacts under output/, and prints the overview. The verification run on 17 July 2026 followed the W3C redirect to https://www.w3.org/news/feed/, parsed RSS 2.0, emitted 25 unique entry records, found zero duplicates, returned a reusable ETag, and passed every configured health check. A second live request using that returned final URL and ETag received HTTP 304; transport passed while parse and freshness correctly remained unknown.

The public Actor requires 512–1024 MB. Its .actor/Dockerfile uses the Node.js 24 Apify image, matches the package's Node 24 engine and npm start command, and the root .dockerignore excludes dependencies, local storage, live demo output, environment files, logs, coverage, tests, and deployment notes.

The Actor is published at https://apify.com/sassy_labs/feed-quality-monitor. The task-example and icon optimization files in deployment/ and assets/ remain local until a separate reviewed publication is approved.