# Substack Publication Delta Monitor (`ursog/substack-publication-delta-monitor`) Actor

- **URL**: https://apify.com/ursog/substack-publication-delta-monitor.md
- **Developed by:** [andre donda de sa](https://apify.com/ursog) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Substack Publication Delta Monitor

Monitor official public Substack RSS feeds and receive only meaningful post changes. The first successful run saves a free baseline; later runs emit and bill only new, updated, or removed-from-observed-feed posts.

This is a monitoring product, not an archive or paywall scraper. It uses public RSS only—no article crawling, comments, likes, subscribers, private endpoints, browser, proxy, AI, or authentication.

### Quick start

1. Add publication home URLs or exact `/feed` URLs.
2. Keep the monitor key unchanged on scheduled runs.
3. Run once to create the expected free baseline.
4. Schedule the same input to receive later changes.

```json
{ "publicationUrls": ["https://doomberg.substack.com"], "monitorKey": "doomberg-monitor" }
```

### Example output

```json
{ "recordType": "change", "changeType": "updated", "guid": "post-123", "title": "Revised title", "publication": "Example", "previousPost": { "contentHash": "old" }, "currentPost": { "contentHash": "new" } }
```

### Pricing

Provisional pricing is **$0.003 per post change**. New, updated, and removed-from-observed-feed events are charged. Baselines, unchanged posts, aliases, retries, diagnostics, empty feeds, and failures are free.

### Inputs

| Input | Default | Purpose |
|---|---:|---|
| `publicationUrls` | `[]` | Substack publication home pages |
| `feedUrls` | `[]` | Official feed URLs ending in `/feed` |
| `monitorKey` | `default` | Persistent namespace |
| `maxPublications` | `25` | Hard maximum 100 with advanced mode |
| `maxPostsPerFeed` | `20` | Bounded observed feed window |
| `maxConcurrency` | `5` | Concurrent public feeds |
| `requestTimeoutSecs` | `15` | Whole transaction timeout |
| `maxRequestRetries` | `1` | Temporary failure retries |
| `maxResponseBytes` | `2000000` | Streaming limit per feed |

### Output

Changes include GUID, title, publication, published/updated dates, author, canonical feed URL, content hash, previous/current records, public provenance, and observation time. Missing source fields remain `null`. Raw post bodies are neither output nor persisted.

Statuses distinguish complete, empty, partial, and blocked runs. A blocked or invalid feed is never reported as a successful empty result.

### Limitations

- Only RSS responses declaring `generator=Substack` are accepted.
- RSS is a bounded rolling window, not full history.
- `removed_from_observed_feed` does not prove deletion, unpublishing, or retraction.
- Changing feed depth can create apparent additions or removals; use a new monitor key.
- Custom domains must resolve only to public networks and remain HTTPS through redirects.
- Updated dates remain `null` when Substack does not publish one.

### Public-data boundary

The Actor reads public `/feed` responses only. It does not open post pages or bypass paywalls, logins, private publications, or access controls. If reliable operation later requires a proxy, browser, account, or private endpoint, the product must stop instead of bypassing controls.

### FAQ

**Why was the first run a baseline?** Existing posts must be learned before later changes can be detected; they are not billed.

**Does it copy post content?** No. It stores a normalized SHA-256 hash of the content publicly present in RSS.

**Why did a post leave the feed?** A rolling feed can push older posts outside the visible window. The Actor deliberately avoids claiming deletion.

### Support

Provide the private run URL, feed URL, and diagnostic code. Never send Substack credentials; the Actor cannot use them.

See [CHANGELOG.md](CHANGELOG.md).

# Changelog

This Actor's version history is a separate document: https://apify.com/ursog/substack-publication-delta-monitor/changelog.md

# Actor input Schema

## `publicationUrls` (type: `array`):

Publication home pages such as https://example.substack.com or https://www.example.com.

## `feedUrls` (type: `array`):

Official Substack RSS URLs ending in /feed.

## `monitorKey` (type: `string`):

Identifies the saved baseline used to compare later runs. Keep it unchanged for the same monitor.

## `maxPublications` (type: `integer`):

Maximum number of unique publications accepted in one run.

## `enableAdvancedLimit` (type: `boolean`):

Explicitly enables runs containing up to 100 publications.

## `maxPostsPerFeed` (type: `integer`):

Maximum posts retained from each bounded RSS response. Changing this can change removal observations.

## `maxConcurrency` (type: `integer`):

Maximum number of public RSS requests performed concurrently.

## `requestTimeoutSecs` (type: `integer`):

Whole-transaction timeout for each feed request, including response streaming.

## `maxRequestRetries` (type: `integer`):

Retries transient network and server failures; blocked sources are not treated as empty feeds.

## `maxResponseBytes` (type: `integer`):

Streaming byte limit per feed. Legitimate content-heavy feeds can exceed 1 MB.

## Actor input object example

```json
{
  "publicationUrls": [],
  "feedUrls": [],
  "monitorKey": "default",
  "maxPublications": 25,
  "enableAdvancedLimit": false,
  "maxPostsPerFeed": 20,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 15,
  "maxRequestRetries": 1,
  "maxResponseBytes": 2000000
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `baselines` (type: `string`):

No description

## `diagnostics` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("ursog/substack-publication-delta-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("ursog/substack-publication-delta-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call ursog/substack-publication-delta-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ursog/substack-publication-delta-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/GXYzmd7yIPEx98ixk/builds/q0K9YMR4VbUnZQBpQ/openapi.json
