# Journalist Requests Scraper - #JournoRequest & HARO Alternative (`monriah-studio/journalist-requests`) Actor

Collect fresh journalist source requests (#journorequest, #prrequest, #mediarequest) from public Bluesky and Mastodon APIs and your own public RSS feeds. Outlet, deadline, topic tags, dedupe, since-date and keyword filters. No login.

- **URL**: https://apify.com/monriah-studio/journalist-requests.md
- **Developed by:** [MonRiah Studio](https://apify.com/monriah-studio) (community)
- **Categories:** News, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 journalist requests

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## 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

## Journalist Requests Scraper – #JournoRequest & HARO Alternative

A test run with the default input (last 14 days, Bluesky and Mastodon) returns about 55 requests and costs about $0.11 ($0.002 per result, plus a $0.00005 start fee; capped at 100 results, about $0.20). Apify's free plan includes monthly platform credit.

**Journalist Requests Scraper** collects fresh **journalist source requests**, **#journorequest** posts, **#PRrequest** / **#mediarequest** call-outs and (optionally) **podcast guest requests** into one clean, deduplicated dataset. Each row gives you the **request text, outlet, journalist name and handle, topic tags, deadline, posted date and source link**, so you can pitch the right journalists before the deadline.

It's a **HARO alternative** for digital PR, link building and founder-led PR. No HARO/Connectively, Qwoted, Featured or SourceBottle account is needed, and **no login, no cookies, no proxy** are used: the Actor reads only public, documented APIs and the public RSS feeds you choose.

### What can this journalist request scraper do?

- **One feed of media requests** from Bluesky (most volume) and Mastodon hashtag timelines, plus any public RSS/Atom feed you add.
- **Normalized records**: `title`, `requestText`, `outlet`, `journalistName`, `journalistHandle`, `topicTags`, `deadline`, `postedAt`, `sourceUrl`.
- **Deadline parsing**: "Deadline 24 Oct, 12:00", "by Friday 5pm", "today", "ASAP" → ISO `deadline`, `deadlineText`, `isUrgent`, `isExpired`.
- **Outlet detection**: "writing for Brewer & Distiller", "wanted for The i Paper", "(The Guardian)" → `outlet`.
- **17 auto topic tags** (business & finance, health, tech, travel, food & drink, home & property, parenting, career, marketing & PR, environment, legal & politics, science, entertainment, case studies, products & gift guides, podcast guests…).
- **Deduplication** within a run (same post found via several hashtags or instances, aggregator double-posts, cross-posted text) and **across runs** with *Only new requests since last run*.
- **Filters**: keywords, topic categories, **since-date** (absolute or relative such as `3 days`), hide expired, genuine-requests-only, exclude replies, exclude aggregator re-posts.
- **No free-tier holdbacks**: every result you pay for is the latest one, with no 25-row cap or 48-hour delay.

### How to use it

1. Click **Try for free**.
2. Add the **keywords** you can speak about (e.g. `cybersecurity`, `personal finance`, `SaaS`) or pick **topic categories**, or leave both empty to see everything.
3. Choose **Posted since** (default: last 14 days) and **Max results**.
4. Click **Start**. The default run takes under a minute.
5. Open the **Requests** view, or the **Pitch queue** view for the full text and reply links.
6. For a daily alert: create a **Schedule**, turn on **Only new requests since last run**, and connect Slack, email, Google Sheets, Zapier, Make or n8n through Apify integrations.

### Input example

```json
{
  "keywords": ["finance", "retirement", "landlord"],
  "categories": [],
  "maxResults": 100,
  "sinceDate": "7 days",
  "hideExpired": true,
  "sources": ["bluesky", "mastodon"],
  "hashtags": ["journorequest", "journorequests", "prrequest", "mediarequest", "sourcerequest"],
  "includePodcastGuestRequests": false,
  "mastodonInstances": ["mastodon.social", "journa.host"],
  "rssFeedUrls": [],
  "onlyNewSinceLastRun": true
}
```

| Input | What it does |
|---|---|
| `keywords` | Keep requests mentioning any keyword. Matches go to `matchedKeywords`. |
| `categories` | Keep requests with at least one of these auto topic tags. |
| `maxResults` | Max requests returned, newest first. |
| `sinceDate` | `YYYY-MM-DD` or relative (`24 hours`, `3 days`, `2 weeks`). |
| `hideExpired` | Drop requests whose parsed deadline has passed. |
| `onlyLikelyRequests` | Skip hashtag posts that aren't requests (press releases, jokes). On by default. |
| `excludeAggregators` | Hide accounts that re-post requests from other platforms. |
| `excludeReplies` | Skip thread replies. On by default. |
| `sources` | `bluesky`, `mastodon`, `rss`. |
| `hashtags` | Hashtags to search (without `#`). |
| `includePodcastGuestRequests` | Adds `#podcastguest`, `#podcastguests`, `#guestrequest`. |
| `mastodonInstances` | Mastodon servers whose public hashtag timelines are read. |
| `rssFeedUrls` | Your own public RSS/Atom feeds of requests or newsletter archives. |
| `onlyNewSinceLastRun` / `stateStoreName` | Cross-run dedupe for schedules. |

### Output example

One item per request. Download as JSON, CSV, Excel, HTML or XML, or use the API.

```json
{
  "requestId": "bluesky:eb6e64c8be103e03",
  "title": "Ski helmet buying tips and new products for Snow Show piece (PTMollie.com).",
  "requestText": "Ski helmet buying tips and new products for Snow Show piece (PTMollie.com). Deadline 18 Oct, 0:59.\n\n#JournoRequest — the brief and how to reply: https://www.thesourceradar.com/r/cmv9go8l1000y89didwgz4iox",
  "outlet": "PTMollie.com",
  "journalistName": null,
  "journalistHandle": "@thesourceradar.bsky.social",
  "journalistProfileUrl": "https://bsky.app/profile/thesourceradar.bsky.social",
  "isAggregatorPost": true,
  "topicTags": ["travel-leisure", "products-reviews"],
  "matchedKeywords": [],
  "hashtags": ["journorequest"],
  "deadline": "2026-10-18T00:59:00.000Z",
  "deadlineText": "Deadline 18 Oct, 0:59",
  "isUrgent": false,
  "isExpired": true,
  "postedAt": "2026-10-16T11:30:22.955Z",
  "sourceUrl": "https://bsky.app/profile/thesourceradar.bsky.social/post/3mxyhyr3wbc2l",
  "replyLinks": ["https://www.thesourceradar.com/r/cmv9go8l1000y89didwgz4iox"],
  "source": "bluesky",
  "sourceName": "Bluesky",
  "matchedHashtag": "journorequest",
  "language": "en",
  "engagement": { "likes": 0, "reposts": 1, "replies": 0 },
  "scrapedAt": "2026-10-19T18:37:16.278Z"
}
```

```json
{
  "title": "I'm looking for international stories (for a British paper) with lines on: -migration -overtourism -in-depth, expert-led analysis (with…",
  "outlet": "a British paper",
  "journalistName": "Lauren Crosby Medlicott",
  "journalistHandle": "@laurenmedlicott.bsky.social",
  "topicTags": ["travel-leisure", "legal-politics"],
  "deadline": null,
  "postedAt": "2026-10-15T15:33:06.128Z",
  "sourceUrl": "https://bsky.app/profile/laurenmedlicott.bsky.social/post/3mxwf3tut622n"
}
```

The key-value store record `SUMMARY` shows how many posts were fetched per source and how many were removed as duplicates, non-requests or by your filters.

### Sources and why they're allowed

| Source | How it's accessed | Why it's allowed |
|---|---|---|
| **Bluesky** | Official public AppView API `api.bsky.app/xrpc/app.bsky.feed.searchPosts` (no account) | Bluesky's robots.txt points automated readers to its API and allows crawling. The ToS and Developer Guidelines prohibit spam, not reading public posts. This Actor only reads posts and never posts, likes or messages. |
| **Mastodon** | Each instance's public REST API `/api/v1/timelines/tag/{tag}` (defaults: mastodon.social, journa.host) | This is a public, unauthenticated, documented API. robots.txt doesn't disallow it. Only `public` posts are read. Posts mirrored from X/Twitter and Bluesky bridges are skipped. |
| **RSS/Atom (optional)** | Only feed URLs that you supply | You choose the feeds. Only add feeds whose terms allow automated reading. |

**Not used, on purpose:** HARO/Connectively, Qwoted, Featured, Help a B2B Writer/MentionMatch, Muck Rack, ResponseSource and other logged-in or paywalled platforms. **SourceBottle** is also excluded because its terms forbid reproducing or commercially exploiting its content. **X/Twitter** is excluded because X data may only be accessed through X's official paid API. Some posts on Bluesky are re-posted by aggregator accounts. They are flagged with `isAggregatorPost` and can be excluded.

### Tips and notes

- Journalists rarely put a deadline in social posts, so `deadline` is often `null`. Social requests usually go stale within a week or two, so use `sinceDate` to keep it fresh.
- Deadlines without a timezone are read as UTC (most #journorequest posters are UK-based).
- Outlet and topic detection are rule-based and best-effort. The full `requestText` is always included.
- Please pitch responsibly: reply only when you're a genuine fit. Don't add journalists to mailing lists.
- Not affiliated with Bluesky, Mastodon, HARO, or any outlet.

### Pricing

Pay per result: **$2.00 per 1,000 requests delivered** (`result` event), plus Apify's tiny actor-start fee. Duplicates, filtered-out posts and requests skipped by *Only new requests* are **not charged**. A daily run with keyword filters usually costs a few cents.

### Use from an AI agent

You can give this Actor to an AI agent as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) (`https://mcp.apify.com`), from Claude, Cursor, VS Code and other MCP clients. Add the server with only this Actor loaded:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=monriah-studio/journalist-requests"
    }
  }
}
```

On first connection your browser opens to sign in to Apify (OAuth). Or add `"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }` to use your API token instead. Then ask your agent something like "Find journalist requests about fintech from the last two weeks".

You can also call it directly via the [Apify API](https://docs.apify.com/api/v2), for example to run it and get the results in one request:

```bash
curl -X POST "https://api.apify.com/v2/acts/monriah-studio~journalist-requests/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "keywords": ["fintech"],
  "sinceDate": "14 days",
  "maxResults": 50
}'
```

### More actors from MonRiah Studio

- [Greenhouse, Lever, Ashby & Workday Jobs Scraper](https://apify.com/monriah-studio/ats-hiring-signals): open roles and hiring signals from 6 ATS job boards, with change detection.
- [SEC Form 4 Insider Trading Scraper](https://apify.com/monriah-studio/sec-form4-insider-trades): insider trading data from official SEC EDGAR Form 4 filings.
- [RSS Feed Scraper & Monitor](https://apify.com/monriah-studio/rss-feed-monitor): RSS to JSON for any RSS or Atom feed, with new-item change detection.
- [Grants.gov Funding Opportunities Scraper](https://apify.com/monriah-studio/grants-gov-opportunities): federal grant opportunities from the official Grants.gov API, with award amounts, deadlines, eligibility and new-grant alerts.
- [Bluesky Mentions Monitor](https://apify.com/monriah-studio/bluesky-mentions): brand, keyword and hashtag mentions on Bluesky, with engagement, sentiment and only-new alerts.

# Actor input Schema

## `keywords` (type: `array`):

Keep only requests whose text contains any of these words or phrases (case-insensitive), e.g. `cybersecurity`, `personal finance`, `SaaS`. Matches are listed in `matchedKeywords`. Leave empty to get every request.

## `categories` (type: `array`):

Keep only requests auto-tagged with at least one of these topics. Leave empty for all topics.

## `maxResults` (type: `integer`):

Maximum number of requests to return (newest first). You pay only for results delivered.

## `sinceDate` (type: `string`):

Only requests posted on or after this date. Use an absolute date (YYYY-MM-DD) or a relative period such as `3 days` or `24 hours`.

## `hideExpired` (type: `boolean`):

Drop requests with a parsed deadline in the past. Requests with no stated deadline are kept.

## `onlyLikelyRequests` (type: `boolean`):

Skip hashtag posts that don't read like a request (press releases, jokes, announcements). Checks for language such as 'looking for', 'seeking', 'wanted', 'deadline', 'DM me'.

## `excludeAggregators` (type: `boolean`):

Hide posts from accounts that re-post requests from other platforms (they often link to a sign-up page). Off by default; such rows are flagged with `isAggregatorPost`.

## `excludeReplies` (type: `boolean`):

Skip replies in threads, which are rarely new requests.

## `sources` (type: `array`):

Public sources to read. Bluesky has the most volume. RSS reads only the feed URLs you add below.

## `hashtags` (type: `array`):

Hashtags to search on Bluesky and Mastodon (without #).

## `includePodcastGuestRequests` (type: `boolean`):

Also search #podcastguest, #podcastguests and #guestrequest.

## `mastodonInstances` (type: `array`):

Instances whose public hashtag timelines are read. Large instances also show federated posts from other servers.

## `rssFeedUrls` (type: `array`):

Optional public RSS or Atom feeds of source requests or newsletter archives. Only add feeds you are allowed to read automatically. Every item is treated as a request. Requires the RSS source to be selected.

## `onlyNewSinceLastRun` (type: `boolean`):

For scheduled runs: return only requests not delivered in earlier runs. State is kept in a named key-value store.

## `stateStoreName` (type: `string`):

Named key-value store used by 'Only new requests'. Use a different name per schedule.

## Actor input object example

```json
{
  "keywords": [],
  "categories": [],
  "maxResults": 100,
  "sinceDate": "14 days",
  "hideExpired": false,
  "onlyLikelyRequests": true,
  "excludeAggregators": false,
  "excludeReplies": true,
  "sources": [
    "bluesky",
    "mastodon"
  ],
  "hashtags": [
    "journorequest",
    "journorequests",
    "prrequest",
    "mediarequest",
    "sourcerequest"
  ],
  "includePodcastGuestRequests": false,
  "mastodonInstances": [
    "mastodon.social",
    "journa.host"
  ],
  "rssFeedUrls": [],
  "onlyNewSinceLastRun": false,
  "stateStoreName": "journalist-requests-state"
}
```

# Actor output Schema

## `results` (type: `string`):

All requests delivered by this run.

## `summary` (type: `string`):

JSON record SUMMARY: counts fetched per source, duplicates removed, filtered and delivered.

# 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 = {
    "keywords": [],
    "maxResults": 100,
    "sinceDate": "14 days",
    "sources": [
        "bluesky",
        "mastodon"
    ],
    "hashtags": [
        "journorequest",
        "journorequests",
        "prrequest",
        "mediarequest",
        "sourcerequest"
    ],
    "mastodonInstances": [
        "mastodon.social",
        "journa.host"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("monriah-studio/journalist-requests").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 = {
    "keywords": [],
    "maxResults": 100,
    "sinceDate": "14 days",
    "sources": [
        "bluesky",
        "mastodon",
    ],
    "hashtags": [
        "journorequest",
        "journorequests",
        "prrequest",
        "mediarequest",
        "sourcerequest",
    ],
    "mastodonInstances": [
        "mastodon.social",
        "journa.host",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("monriah-studio/journalist-requests").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 '{
  "keywords": [],
  "maxResults": 100,
  "sinceDate": "14 days",
  "sources": [
    "bluesky",
    "mastodon"
  ],
  "hashtags": [
    "journorequest",
    "journorequests",
    "prrequest",
    "mediarequest",
    "sourcerequest"
  ],
  "mastodonInstances": [
    "mastodon.social",
    "journa.host"
  ]
}' |
apify call monriah-studio/journalist-requests --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,monriah-studio/journalist-requests"
        }
    }
}
```

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/BptKelBVK0K26c24q/builds/V9GRyGdmhtb6HGkry/openapi.json
