# Publishing Frequency Tracker - Blog Content Volume Clay (`mambalabs/blog-publishing-frequency`) Actor

Measures how much long-form work a company publishes per month and whether that rate is rising or falling. Returns a flat Clay-ready row with 30 day, 90 day and 12 month post counts, a cadence trend, format mix and byline count. Editorial output volume, not changelog monitoring.

- **URL**: https://apify.com/mambalabs/blog-publishing-frequency.md
- **Developed by:** [Mamba Labs](https://apify.com/mambalabs) (community)
- **Categories:** Lead generation, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.40 / 1,000 domain analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### 📝 What can Publishing Frequency Tracker do?

Give it a **company domain** and it returns **how much long-form content that company publishes and
whether the rate is rising or falling**: posts per month over the last 12 months, the trend against
its own baseline, and the formats. One flat row per domain.

The trend is the product. A company publishing four pieces a month is unremarkable. A company that
went from four to twelve in six months has a content operation outgrowing its staff.

| 📦 What you get | ⚙️ Features and integrations |
|---|---|
| 📈 **`cadence_trend`**, recent 90 days against its own baseline<br>🔢 **Posts per month** at 30, 90 and 365 days<br>🏷️ **Ten content formats** counted inside the window<br>🧾 **45 flat fields**, `snake_case`, one row per domain | 🧭 **Blog root discovery** across hosts, feeds and paths<br>🛡️ **Reliability guard** that nulls counts it cannot trust<br>⏱️ **Hard per-domain time budget**, never runs away<br>⬇️ **Export** to JSON, CSV, Excel, HTML or XML |

Bought by teams selling into content, SEO and demand generation, where a company outgrowing its
publishing capacity is the buying signal.

> 🚫 **This measures editorial output volume. It is not a changelog monitor.** Every existing
> supplier in this corner of the Store watches product changelogs and release notes, which is a
> different question with a different buyer. If a feed on the site turns out to be a release feed,
> this actor rejects it rather than counting shipped features as published articles.

### 💡 Why use Publishing Frequency Tracker?

| If you want | Read these fields |
|---|---|
| The buying signal | `cadence_trend`, `trend_pct_change` |
| A rate you can act on | `avg_posts_per_month`, `posts_last_90d` |
| Whether they have gone quiet | `days_since_last_post`, `most_recent_post_date` |
| What kind of content it is | `formats_detected`, `formats_breakdown` |
| The size of the team behind it | `distinct_authors_count`, `authors` |
| Where the numbers came from | `publication_url`, `discovery_method`, `evidence` |
| Whether to quote the number | `date_source_reliable`, `counts_are_estimate`, `confidence` |

#### 📊 The two windows, stated rather than left for you to guess

| Window | Span |
|---|---|
| **recent** | the last 90 days |
| **baseline** | the 275 days before that, day 91 through day 365 |

Both are converted to posts per month before they are compared, so the unequal window lengths do
not bias the ratio.

| `cadence_trend` | Means |
|---|---|
| `accelerating` | recent rate is 25 percent or more above baseline |
| `steady` | within 25 percent either way |
| `declining` | recent rate is 25 percent or more below baseline |
| `dormant` | nothing in 12 months, or nothing in 90 days with the last post over 180 days ago |
| `unknown` | the window could not be established, or the dates could not be trusted |

`trend_pct_change` carries the number. It is `null` when the baseline is zero, because a percentage
against zero is undefined, and `cadence_trend` carries the meaning in that case.

### 📋 What data can Publishing Frequency Tracker extract?

**45 fields** per domain, in four groups.

| Group | Fields |
|---|---|
| The rate | `posts_last_30d`, `posts_last_90d`, `posts_last_12m`, `posts_baseline_window`, `avg_posts_per_month`, `recent_rate_per_month`, `baseline_rate_per_month` |
| The signal | `cadence_trend`, `trend_pct_change`, `most_recent_post_date`, `days_since_last_post`, `oldest_post_seen_date` |
| The shape of the output | `formats_detected`, `formats_breakdown`, `distinct_authors_count`, `authors` |
| Where it came from | `publication_url`, `blog_host`, `discovery_method`, `feed_url`, `feed_item_count`, `post_urls_found`, `posts_dated`, `dating_coverage`, `dating_sources`, `sitemaps_fetched`, `pages_fetched`, `pages_blocked` |
| How much to trust it | `confidence`, `date_source_reliable`, `counts_are_estimate`, `window_reachable`, `partial_result`, `budget_exhausted`, `lastmod_available`, `evidence`, `signals_fired`, `signals_missing` |

`formats_detected` covers `blog_post`, `whitepaper`, `case_study`, `report`, `guide`, `podcast`,
`video`, `press_release`, `research` and `news`, counted only inside the 12 month window so an old
archive of case studies does not describe what a company publishes now.

> ⚠️ **Read `date_source_reliable` and `counts_are_estimate` before quoting a number to anyone.**
> When the reliability guard fires, every count is nulled and `cadence_trend` becomes `unknown`.
> The row still reports where the company publishes and when it last did, because those do not
> depend on the rate. `distinct_authors_count` undercounts by design: plenty of sites publish no
> byline, so zero means "no byline published", never "nobody writes".

### 🛠️ How to measure a company's publishing frequency

1. Open the **Input** tab and put a domain in `domain`, or a list in `domains`.
2. Leave the defaults alone for a first pass. `max_pages_to_date` at 400 dates most publishers
   outright in about 15 seconds.
3. Click **Start**.
4. Read `cadence_trend` for the signal and `avg_posts_per_month` for the rate.
5. Check `date_source_reliable` and `counts_are_estimate` before you put a number in an email.

#### 🧪 Using it in Clay

Add an Apify enrichment column and map your domain column to `domain`. Every field lands as its own
column.

Filter on `cadence_trend = "accelerating"` for companies scaling content, and on `dormant` with a
high `posts_last_12m` for companies that used to publish and stopped, which is a different and
often better conversation.

#### 🎯 Getting a tighter number on a big archive

When every candidate post can be dated inside the page budget, the counts are a census. When a
company has more posts than the budget allows, an even sample across the whole archive is dated and
scaled, and `counts_are_estimate` is set true. Raise `max_pages_to_date` for a tighter number.

### 💵 How much does it cost to track publishing frequency?

You are charged once per domain analyzed.

| Plan | Per domain | Per 1,000 domains |
|---|---|---|
| Free | $0.004 | $4.00 |
| Bronze | $0.0038 | $3.80 |
| Silver | $0.0036 | $3.60 |
| Gold | $0.0034 | $3.40 |

There is also an Actor start event at $0.00005, charged once per run per GB of memory.

> 💳 **One charge per domain, whether it dated 12 posts or 400.** A deep archive crawl and a small
> blog cost the same. Free Apify plans get 15 results per calendar month; paid plans are unlimited.
> Repeat lookups inside 3 days are served from cache.

### ⌨️ Input

Everything is on the **Input** tab. The options worth explaining:

| Field | Type | Default | What it does |
|---|---|---|---|
| `domain` | string | `zapier.com` | One domain. |
| `domains` | array | `[]` | Batch. Takes precedence over `domain`. |
| `max_pages_to_date` | integer | `400` | Post pages fetched per domain. Above this, counts are estimated from an even sample. |
| `domain_time_budget_ms` | integer | `75000` | Hard per-domain ceiling. |
| `page_concurrency` | integer | `8` | Page fetches at once within one domain. |
| `max_sitemap_fetches` | integer | `18` | Sitemap documents per domain. Blog sections are visited first. |
| `batchSize` | integer | `2` | Concurrent domains, 1 to 8. Multiplies against `page_concurrency`. |
| `request_timeout_ms` | integer | `9000` | Per-request timeout. |
| `skipCache` | boolean | `false` | Ignore the 3 day result cache. |

### 📤 Output

One flat row per domain, exportable as **JSON, CSV, Excel, HTML or XML**.

```json
{
  "domain": "zapier.com",
  "publication_url": "https://zapier.com/blog/",
  "blog_host": "zapier.com",
  "discovery_method": "feed",
  "posts_last_90d": 96,
  "posts_last_12m": 372,
  "avg_posts_per_month": 31,
  "recent_rate_per_month": 32,
  "baseline_rate_per_month": 30.1,
  "cadence_trend": "steady",
  "trend_pct_change": 6.3,
  "most_recent_post_date": "2026-08-05",
  "days_since_last_post": 2,
  "formats_detected": ["blog_post", "guide"],
  "distinct_authors_count": 24,
  "date_source_reliable": true,
  "counts_are_estimate": false,
  "confidence": 0.9,
  "evidence": ["blog root https://zapier.com/blog/ (via feed)"],
  "is_summary_row": false
}
```

Every run emits at least one row. Rows with `is_summary_row: true` are notices (empty input, free
tier reached, QA run), not data.

### 💡 Tips

- Prefer `posts_last_90d` and `avg_posts_per_month` for anything you act on. `posts_last_30d` is
  the noisiest field on a large archive.
- When the call is close to a trend boundary, read `avg_posts_per_month` rather than the label.
- `evidence` is quotable. It holds strings like `blog root https://zapier.com/blog/ (via feed)` and
  `each of the 399 dated sample posts stands for 10.6 posts`.
- A `dormant` company with a big `posts_last_12m` used to publish and stopped. That is often a
  better conversation than one that never started.

### ⚠️ Known limits

**Sitemap `lastmod` is never used as a publication date, and that is a measured decision.** Across
100 URL pairs on six sites, `lastmod` matched the page's own `dateModified` within 7 days on 95 of
them, median difference 0 days, while running later than `datePublished` by a median of 151, 195,
776 and 1,653 days on four of the six sites. Building a cadence on it would turn a company that
refreshed 200 old posts last month into a company that published 200 posts last month.
Concentration heuristics do not rescue it either: three of the four badly wrong sites passed a
distinct-days spread test comfortably.

**Some sites reset the publication date when they refresh a post.** The reliability guard catches
the egregious cases and returns no cadence. On buffer.com, `<time datetime>` dated 70 percent of the
year's output to the last 30 days against a feed showing 27 posts a month, and that domain returns
no cadence rather than a confident wrong one. Milder cases read as slightly more recent output than
really happened, which biases toward `accelerating`.

**Large publishers rate limit.** hubspot.com began answering 429 with a browser challenge partway
through the test session, after repeated crawls from one IP. That is reported in `pages_blocked`
with lowered confidence rather than pretending the numbers are complete. No proxy is configured by
default.

**A blog behind JavaScript with no sitemap and no feed is invisible.** There is no headless browser
here. `discovery_method: none` means nothing machine readable was found, not that the company does
not publish.

**Estimated runs vary a little between runs.** When `counts_are_estimate` is true the numbers come
from a sample, so two runs of the same domain can differ by a few posts. Two runs of savanta.com in
testing returned 47 and 56 posts for the year.

**Detection rates, measured live on 9 domains at defaults, 2026-08-07:** publishers with a rate
recovered 5 of 6, controls correctly returning nothing 3 of 3, 12 month window reachable 6 of 6.
The one miss is buffer.com, refused by the reliability guard. Page dating itself, once the blog root
is identified, ran at 92 to 100 percent across the sites measured.

### ❓ FAQ

##### Why did a company I know publishes return nothing?

Either its blog is behind JavaScript with no sitemap and no feed, or the reliability guard refused
the dates. `discovery_method` and `date_source_reliable` tell you which.

##### How does it find the blog when it is not at `/blog`?

Three ways, all of which were failure modes in the feasibility probe. The blog often lives on a
different host from the apex and the apex sitemap never mentions it, so sitemaps are crawled at the
blog host and blog path too. A homepage's only advertised feed is sometimes a changelog, so feeds
are scored and changelog feeds are rejected outright. Locale-prefixed paths defeat naive clustering,
so clustering skips locale segments and `publication_url` reports the shape, for example
`https://ipsos.com/*/knowledge/`.

##### Can a deep archive crawl turn into a run timeout?

No. Every domain carries a hard wall-clock budget, 75 seconds by default. When it is nearly spent
the crawl stops, the row is emitted with what was gathered, `partial_result` is set and confidence
drops.

##### How fast is it?

9 to 75 seconds per domain, mean 36 across the live test. No headless browser, no proxy, no LLM.

##### Why is `trend_pct_change` null?

The baseline was zero, and a percentage against zero is undefined. `cadence_trend` carries the
meaning instead.

### 🧩 Want other GTM data?

Mamba Labs builds custom actors for B2B go-to-market teams. The public versions
of that work live here on the Store, so our users get the same tooling we build
under contract.

| | |
|---|---|
| 🧑‍💼 [GTM Hiring Signal Scraper](https://apify.com/mambalabs/gtm-hiring-signal-scraper) | 🧱 [Tech Stack Detector](https://apify.com/mambalabs/gtm-tech-stack-signal-scraper) |
| 📡 [B2B Buying Signals Aggregator](https://apify.com/mambalabs/b2b-buying-signals-hiring-tech-stack-intent-for-clay) | 🔑 [Job Board Keyword Scanner](https://apify.com/mambalabs/job-board-keyword-signal-scanner) |
| 🔗 [Domain to LinkedIn URL Resolver](https://apify.com/mambalabs/domain-to-linkedin-url-resolver) | 🎯 [ICP Fit Scorer](https://apify.com/mambalabs/icp-account-lead-scoring-fit-scorer-0-100-for-clay) |
| 📋 [Job Posting Monitor](https://apify.com/mambalabs/gtm-job-discovery) | 📬 [Domain Deliverability Checker](https://apify.com/mambalabs/domain-deliverability-checker) |
| 🏢 [Company Firmographic Enricher](https://apify.com/mambalabs/company-firmographic-enricher) | 🌐 [Company Social Presence Mapper](https://apify.com/mambalabs/company-social-presence-mapper) |
| 🪪 [Company Identity Resolver](https://apify.com/mambalabs/company-identity-resolver) | 💰 [Funding and Press Signal Scanner](https://apify.com/mambalabs/funding-press-signal-scanner) |
| 🔄 [Company Change-Event Feed](https://apify.com/mambalabs/company-change-event-feed) | 👤 [People Finder and Email Verifier](https://apify.com/mambalabs/people-finder) |
| 🚀 [Prospect Engine](https://apify.com/mambalabs/b2b-prospect-engine) | 🤖 [AI Tooling Detector](https://apify.com/mambalabs/ai-tooling-detector) |
| 📮 [Outbound Stack Detector](https://apify.com/mambalabs/outbound-infrastructure-fingerprint) | ✉️ [Work Email Waterfall Finder](https://apify.com/mambalabs/email-waterfall-orchestrator) |
| ⏩ [Sequencer Lead Push](https://apify.com/mambalabs/clay-to-instantly-smartlead-push) | 🏅 [Workplace Program Detector](https://apify.com/mambalabs/workplace-program-detector) |
| 👥 [Team Page People Extractor](https://apify.com/mambalabs/team-page-people-extractor) | 🧭 [Company Discovery List Builder](https://apify.com/mambalabs/company-discovery-list-builder) |

> Every actor in the suite takes a domain or a company and returns one flat row,
> so they stack in the same Clay table without reshaping anything.

> 🛠️ **Need something custom built for you or your team?** Tell us what you are
> trying to find and we will build it. [Talk to Mamba Labs](https://mambabuilt.com/contact).

### 🆘 Support

Something wrong, or a blog the actor cannot find? Open an issue on the **Issues** tab with the
domain and the row, and we will look at it.

> ℹ️ **Sourcing and legal.** Everything comes from the company's own published sitemaps, feeds and
> post pages, read directly over HTTP with no proxy and no browser. Author names are read from
> published bylines. The row describes what a company published, not what it plans to publish. You
> are responsible for how you use the output.

Built by [Mamba Labs](https://apify.com/mambalabs).

# Actor input Schema

## `domain` (type: `string`):

A single company domain, for example zapier.com. Ignored when Company domains is filled.

## `domains` (type: `array`):

Analyze many domains in one run. Takes precedence over the single domain field.

## `max_pages_to_date` (type: `integer`):

Publication dates come from the post pages themselves, because sitemap lastmod is a modification date and runs later than publication by a median of 151 to 1653 days on the sites tested. This caps how many pages get fetched per domain. When a company has more candidate posts than the cap, an even sample across the archive is dated and the counts are marked as an estimate on the row. 400 dates most publishers outright and costs about 15 seconds.

## `domain_time_budget_ms` (type: `integer`):

Hard wall-clock ceiling per domain. When it is nearly spent the crawl stops and the row is emitted with what was gathered, partial\_result true and reduced confidence, rather than risking a run timeout. 15000 to 240000.

## `page_concurrency` (type: `integer`):

How many post pages to fetch at once within a single domain. 1 to 16. Raising it speeds up dating on large archives but is more likely to draw a rate limit.

## `max_sitemap_fetches` (type: `integer`):

Cap on sitemap and sitemap-index documents fetched per domain. Large sites nest several levels; blog sections are visited first.

## `batchSize` (type: `integer`):

How many domains to analyze at once. 1 to 8. Each domain runs its own page fetch pool, so this multiplies against Page fetch concurrency.

## `request_timeout_ms` (type: `integer`):

Per-request timeout for feeds, sitemaps and post pages. 3000 to 20000.

## `skipCache` (type: `boolean`):

Ignore the 3 day result cache and re-crawl every domain from scratch.

## `qa_test_mode` (type: `boolean`):

Emit a single summary row and skip all network work. Used for automated checks.

## Actor input object example

```json
{
  "domain": "zapier.com",
  "domains": [],
  "max_pages_to_date": 400,
  "domain_time_budget_ms": 75000,
  "page_concurrency": 8,
  "max_sitemap_fetches": 18,
  "batchSize": 2,
  "request_timeout_ms": 9000,
  "skipCache": false,
  "qa_test_mode": false
}
```

# Actor output Schema

## `results` (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 = {
    "domain": "zapier.com",
    "domains": [],
    "max_pages_to_date": 400,
    "domain_time_budget_ms": 75000,
    "page_concurrency": 8,
    "max_sitemap_fetches": 18,
    "batchSize": 2,
    "request_timeout_ms": 9000,
    "skipCache": false,
    "qa_test_mode": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("mambalabs/blog-publishing-frequency").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 = {
    "domain": "zapier.com",
    "domains": [],
    "max_pages_to_date": 400,
    "domain_time_budget_ms": 75000,
    "page_concurrency": 8,
    "max_sitemap_fetches": 18,
    "batchSize": 2,
    "request_timeout_ms": 9000,
    "skipCache": False,
    "qa_test_mode": False,
}

# Run the Actor and wait for it to finish
run = client.actor("mambalabs/blog-publishing-frequency").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 '{
  "domain": "zapier.com",
  "domains": [],
  "max_pages_to_date": 400,
  "domain_time_budget_ms": 75000,
  "page_concurrency": 8,
  "max_sitemap_fetches": 18,
  "batchSize": 2,
  "request_timeout_ms": 9000,
  "skipCache": false,
  "qa_test_mode": false
}' |
apify call mambalabs/blog-publishing-frequency --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mambalabs/blog-publishing-frequency"
        }
    }
}

```

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/TbLwaUUATdYb6wp4N/builds/hgp1AZjWAGDaI695s/openapi.json
