# Ghost Blog Scraper (`crawlerbros/ghost-blog-scraper`) Actor

Scrape posts, pages, tags, authors, and site info from any Ghost CMS-powered blog via Ghost's public Content API - including ghost.org's own /resources, /changelog, and /help sections. Works globally against any Ghost publication by auto-discovering its public Content API key.

- **URL**: https://apify.com/crawlerbros/ghost-blog-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Developer tools, SEO tools, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Ghost Blog Scraper

Scrape posts, pages, tags, authors, and site info from any [Ghost](https://ghost.org) CMS-powered blog — including ghost.org's own `/resources`, `/changelog`, and `/help` publications, or any independent Ghost blog (Platformer, Lenny's Newsletter-style publications, and thousands more). Works via Ghost's official public Content API, with the read-only API key **auto-discovered** from the target site's homepage — no login, no admin credentials, no proxy required.

### What this actor does

- **Global by design:** point it at any Ghost-powered site's URL and it works — the public Content API key every Ghost theme embeds for its member-signup widget is auto-discovered automatically
- **Ten modes:** `posts`, `pages`, `tags`, `authors`, `byPostSlugs`, `byPageSlugs`, `byTagSlugs`, `byAuthorSlugs`, `siteInfo`, `tiers`
- **Post filters:** tag, author, publish-date range, keyword, featured-only, minimum reading time
- **Full metadata:** tags, authors, feature images, SEO/OG/Twitter card fields, reading time, comment/member-access flags
- **Empty fields are omitted** — every record only contains fields that actually have data

### Output fields

#### Posts / Pages (`recordType: "post"` / `"page"`)

| Field | Description |
|---|---|
| `postId`, `uuid`, `slug` | Post identity |
| `title`, `excerpt`, `html`, `plaintext` | Content (plaintext only if `includePlaintext` is set) |
| `featureImage`, `featureImageAlt`, `featureImageCaption` | Cover image |
| `featured` | Whether flagged as featured |
| `visibility` | `public` / `members` / `paid` |
| `publishedAt`, `updatedAt`, `createdAt` | Timestamps |
| `readingTimeMinutes` | Estimated reading time |
| `commentsEnabled`, `access`, `commentId` | Comment/member-access flags, native Ghost comment-thread ID |
| `tags[]`, `tagSlugs[]`, `primaryTag` | Tags (posts only) |
| `authors[]`, `authorSlugs[]`, `primaryAuthor` | Authors (posts only) |
| `emailSubject` | Newsletter email subject line, if sent as a newsletter (posts only) |
| `metaTitle`, `metaDescription`, `canonicalUrl` | SEO fields |
| `ogImage`, `ogTitle`, `ogDescription`, `twitterImage`, `twitterTitle`, `twitterDescription` | Social card fields |
| `postUrl` | Canonical URL |

#### Tags (`recordType: "tag"`)

`tagId`, `name`, `slug`, `description`, `featureImage`, `visibility`, `accentColor`, `metaTitle`, `metaDescription`, `ogImage`, `ogTitle`, `ogDescription`, `twitterImage`, `twitterTitle`, `twitterDescription`, `canonicalUrl`, `tagUrl`

#### Authors (`recordType: "author"`)

`authorId`, `name`, `slug`, `profileImage`, `coverImage`, `bio`, `website`, `location`, social links (`facebook`, `twitter`, `threads`, `bluesky`, `mastodon`, `tiktok`, `youtube`, `instagram`, `linkedin`), `metaTitle`, `metaDescription`, `authorUrl`

#### Membership tiers (`recordType: "tier"`)

`tierId`, `name`, `slug`, `description`, `type` (`free` / `paid`), `visibility`, `active`, `welcomePageUrl`, `currency`, `monthlyPrice`, `yearlyPrice`, `trialDays`, `benefits[]`, `createdAt`, `updatedAt`

Prices are in the smallest currency unit (e.g. cents for USD) as returned by the Content API.

#### Site info (`recordType: "site"`)

`siteTitle`, `siteDescription`, `logo`, `icon`, `accentColor`, `coverImage`, social links, `locale`, `timezone`, `navigation[]`, `secondaryNavigation[]`, `metaTitle`, `metaDescription`, `ogImage`, `ogTitle`, `ogDescription`, `twitterImage`, `twitterTitle`, `twitterDescription`, `codeinjectionHead`, `codeinjectionFoot`, `membersEnabled`, `paidMembersEnabled`, `donationsEnabled`, `recommendationsEnabled`, `commentsEnabled`, `version`, `siteUuid`, `siteUrl`

Every record also includes `recordType` and `scrapedAt` (UTC ISO timestamp).

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `posts` | `posts` / `pages` / `tags` / `authors` / `byPostSlugs` / `byPageSlugs` / `byTagSlugs` / `byAuthorSlugs` / `siteInfo` / `tiers` |
| `siteUrl` | string | `https://ghost.org/resources` | Base URL of any Ghost-powered site |
| `contentApiKey` | string | – | Override, only if auto-discovery fails |
| `apiBaseUrl` | string | – | Override, only if auto-discovery fails |
| `tagSlug` | string | – | mode=posts: filter to a tag slug |
| `authorSlug` | string | – | mode=posts: filter to an author slug |
| `visibility` | select | – | mode=posts: `public` / `members` / `paid` |
| `sortOrder` | string | `newest` | mode=posts/pages: `newest` / `oldest` |
| `dateFrom` | string | – | mode=posts: ISO date, published on/after |
| `dateTo` | string | – | mode=posts: ISO date, published on/before |
| `keyword` | string | – | Case-insensitive substring match on title/excerpt |
| `featuredOnly` | boolean | `false` | Only featured posts |
| `minReadingTime` | integer | – | Drop posts/pages shorter than this many minutes |
| `includePlaintext` | boolean | `false` | Also fetch a plaintext body |
| `postSlugs` | array | – | mode=byPostSlugs: exact slugs to look up |
| `pageSlugs` | array | – | mode=byPageSlugs: exact page slugs to look up |
| `tagSlugs` | array | – | mode=byTagSlugs: exact tag slugs to look up |
| `authorSlugs` | array | – | mode=byAuthorSlugs: exact author slugs to look up |
| `maxItems` | integer | `50` | Hard cap (1–1000) |

#### Example: latest posts from ghost.org's resources hub

```json
{
  "mode": "posts",
  "siteUrl": "https://ghost.org/resources",
  "sortOrder": "newest",
  "maxItems": 25
}
```

#### Example: posts filtered by tag and date range

```json
{
  "mode": "posts",
  "siteUrl": "https://ghost.org/resources",
  "tagSlug": "newsletter",
  "dateFrom": "2025-01-01",
  "maxItems": 20
}
```

#### Example: any independent Ghost blog

```json
{
  "mode": "posts",
  "siteUrl": "https://www.platformer.news",
  "maxItems": 20
}
```

#### Example: single post lookup

```json
{
  "mode": "byPostSlugs",
  "siteUrl": "https://ghost.org/resources",
  "postSlugs": ["grasping-your-growth"]
}
```

#### Example: exact page / tag / author lookups

```json
{ "mode": "byPageSlugs", "siteUrl": "https://ghost.org/resources", "pageSlugs": ["referrals"] }
{ "mode": "byTagSlugs", "siteUrl": "https://ghost.org/resources", "tagSlugs": ["newsletter"] }
{ "mode": "byAuthorSlugs", "siteUrl": "https://ghost.org/resources", "authorSlugs": ["armando"] }
```

### Use cases

- **Content research** — pull a Ghost publication's full archive with metadata
- **Newsletter tracking** — monitor a Ghost-powered newsletter's post cadence and topics
- **Migration / backup** — export posts, tags, and authors from a Ghost site
- **SEO analysis** — audit meta titles, descriptions, and social card fields across posts
- **Competitive monitoring** — track any independent Ghost publication's publishing activity

### FAQs

**Does this require a Ghost admin account or API key?**
No. Ghost's Content API key is a *public*, read-only key that every Ghost theme embeds directly in its homepage HTML (for the built-in member-signup widget). This actor discovers that key automatically — the same way your browser's page already has access to it.

**Will this work on my own Ghost blog?**
Yes — set `siteUrl` to your blog's URL. If your theme doesn't expose the standard member-signup widget (rare), supply `contentApiKey` and `apiBaseUrl` manually; you can find these in your Ghost Admin under Settings → Integrations.

**Why does `ghost.org/resources` return different data than `ghost.org`?**
`ghost.org` itself is a static marketing site, not Ghost CMS. Ghost's own product blog, changelog, and help center live at `ghost.org/resources`, `ghost.org/changelog`, and `ghost.org/help` — each is a genuine Ghost-powered publication reverse-proxied onto the main domain. All three work as `siteUrl` values.

**Can I scrape members-only or paid content?**
No — the Content API only returns what's publicly visible without a member login. Posts with `visibility: members` or `visibility: paid` will show their metadata but the `html`/`plaintext` body will be excerpt-only, matching what a logged-out visitor sees.

**Is this actor global?**
Yes — Ghost is used by thousands of independent publications worldwide with no regional restrictions. Any Ghost site's URL works as input.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

## `siteUrl` (type: `string`):

Base URL of any Ghost-powered site, e.g. `https://ghost.org/resources`, `https://ghost.org/changelog`, or your own Ghost blog's URL. The public Content API key is auto-discovered from the site's homepage.

## `contentApiKey` (type: `string`):

Optional. Only needed if auto-discovery fails — every Ghost theme publicly exposes this read-only key for its member-signup widget. Not the secret Admin API key.

## `apiBaseUrl` (type: `string`):

Optional. Only needed if auto-discovery fails, e.g. `https://yoursite.ghost.io/ghost/api/content/`.

## `tagSlug` (type: `string`):

Only return posts with this tag slug, e.g. `newsletter`.

## `authorSlug` (type: `string`):

Only return posts by this author slug.

## `visibility` (type: `string`):

Only return posts with this visibility level. `members`/`paid` posts still return full metadata, but their `html`/`plaintext` body is excerpt-only unless the site allows public viewing (matches what a logged-out visitor sees).

## `sortOrder` (type: `string`):

Publish-date ordering.

## `dateFrom` (type: `string`):

ISO date, e.g. `2024-01-01`. Only posts published on or after this date.

## `dateTo` (type: `string`):

ISO date, e.g. `2024-12-31`. Only posts published on or before this date.

## `keyword` (type: `string`):

Case-insensitive substring match against the title and excerpt (mode=posts/pages).

## `featuredOnly` (type: `boolean`):

Only return posts flagged as featured (mode=posts).

## `minReadingTime` (type: `integer`):

Drop posts/pages shorter than this estimated reading time.

## `includePlaintext` (type: `boolean`):

Also fetch a stripped plaintext version of the body alongside HTML (mode=posts/pages/byPostSlugs).

## `postSlugs` (type: `array`):

Exact post slugs to look up, e.g. `grasping-your-growth`.

## `pageSlugs` (type: `array`):

Exact static page slugs to look up, e.g. `referrals`.

## `tagSlugs` (type: `array`):

Exact tag slugs to look up, e.g. `newsletter`.

## `authorSlugs` (type: `array`):

Exact author slugs to look up, e.g. `armando`.

## `maxItems` (type: `integer`):

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "posts",
  "siteUrl": "https://ghost.org/resources",
  "contentApiKey": "",
  "apiBaseUrl": "",
  "tagSlug": "",
  "authorSlug": "",
  "visibility": "",
  "sortOrder": "newest",
  "dateFrom": "",
  "dateTo": "",
  "featuredOnly": false,
  "includePlaintext": false,
  "postSlugs": [],
  "pageSlugs": [],
  "tagSlugs": [],
  "authorSlugs": [],
  "maxItems": 50
}
```

# Actor output Schema

## `items` (type: `string`):

Dataset containing all scraped Ghost blog records.

# 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 = {
    "mode": "posts",
    "siteUrl": "https://ghost.org/resources",
    "contentApiKey": "",
    "apiBaseUrl": "",
    "tagSlug": "",
    "authorSlug": "",
    "sortOrder": "newest",
    "dateFrom": "",
    "dateTo": "",
    "featuredOnly": false,
    "includePlaintext": false,
    "postSlugs": [],
    "pageSlugs": [],
    "tagSlugs": [],
    "authorSlugs": [],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/ghost-blog-scraper").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 = {
    "mode": "posts",
    "siteUrl": "https://ghost.org/resources",
    "contentApiKey": "",
    "apiBaseUrl": "",
    "tagSlug": "",
    "authorSlug": "",
    "sortOrder": "newest",
    "dateFrom": "",
    "dateTo": "",
    "featuredOnly": False,
    "includePlaintext": False,
    "postSlugs": [],
    "pageSlugs": [],
    "tagSlugs": [],
    "authorSlugs": [],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/ghost-blog-scraper").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 '{
  "mode": "posts",
  "siteUrl": "https://ghost.org/resources",
  "contentApiKey": "",
  "apiBaseUrl": "",
  "tagSlug": "",
  "authorSlug": "",
  "sortOrder": "newest",
  "dateFrom": "",
  "dateTo": "",
  "featuredOnly": false,
  "includePlaintext": false,
  "postSlugs": [],
  "pageSlugs": [],
  "tagSlugs": [],
  "authorSlugs": [],
  "maxItems": 50
}' |
apify call crawlerbros/ghost-blog-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/0mTYt7viaFtBO2L8g/builds/8sKDH2kgLCzQTXpjk/openapi.json
