# Journalist Request Feed – #JournoRequest Source Call-outs (`gazidev/journalist-request-feed`) Actor

Get journalist source requests (#JournoRequest, #PRrequest) from Bluesky with deadline, outlet, requester, topic and how to respond. Keyword alerts, only-new mode for daily schedules, no login. $3 per 1,000 requests.

- **URL**: https://apify.com/gazidev/journalist-request-feed.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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 Request Feed: #JournoRequest & #PRrequest source call-outs from Bluesky

Get **journalist source requests** (#JournoRequest, #PRrequest, #journorequests, #mediarequest, #seekingsources) from Bluesky as clean, structured rows. Each row has the **topic, outlet, deadline, requester and how to respond**. Schedule it daily with **"Only new requests"** and you get just the fresh call-outs, so it works as a free-standing HARO / Qwoted-style alert feed for PR people, founders and expert sources.

- **No login, no API key.** It uses Bluesky's official public AppView API (`app.bsky.feed.searchPosts`).
- **Parsed fields:** deadline text plus a resolved date, outlet (The Guardian, i Paper, Newsweek, Metro…), topic, category (expert / case study / product / recommendation), and respond method (link / email / DM / reply).
- **Keyword alerts:** keep only requests that mention your niche (e.g. `mortgage`, `nutritionist`, `Guardian`).
- **Dedupe across runs:** a key-value store remembers what you already received for 90 days. Duplicates, already-seen and filtered posts are **never charged**.
- **Junk filtered:** round-up digests, press releases, "expert available" pitches and spam that use the hashtags are skipped.

### Be aware: volume is honest, not huge

Bluesky is the **only** source. Most #JournoRequest traffic is still on X/Twitter and in paid platforms, which this Actor does **not** scrape. On Bluesky the hashtags carry about **4–8 genuine requests per day** (roughly 120–200 a month), mostly UK national press, women's magazines and consumer/finance desks. Around half are re-posted by aggregators (The Source Radar, Media Matchmaker, Synapse) with a link to the full brief.

Anonymous Bluesky search returns the newest ~100 posts per hashtag. For #JournoRequest that is about 3 weeks, so a **daily or hourly schedule** never misses anything.

### Use cases

- **PR agencies & freelancers:** a morning digest of new media opportunities for your clients' topics.
- **Founders & experts:** get press coverage by answering call-outs in your niche (keyword filter + daily schedule + email/Slack integration).
- **Newsrooms & researchers:** track what journalists are asking for and which outlets are most active.
- **AI agents:** feed new opportunities into an agent that drafts pitches (see the MCP section).

### Input example

```json
{
  "hashtags": ["journorequest", "journorequests", "prrequest", "mediarequest", "seekingsources", "journalistrequest"],
  "keywords": ["mortgage", "finance", "money"],
  "lookbackDays": 7,
  "onlyNew": true,
  "stateName": "finance-alerts",
  "skipExpired": true
}
```

| Field | What it does |
|---|---|
| `hashtags` | Tags to watch (default: the 6 with real volume on Bluesky) |
| `keywords` / `excludeKeywords` | Keep / drop requests by words in text, outlet or requester |
| `lookbackDays` | Only posts from the last N days (1–60) |
| `onlyNew` + `stateName` | Return only requests not seen in earlier runs with the same state name |
| `includeAggregators` | Include The Source Radar / Media Matchmaker / Synapse re-posts |
| `skipNonRequests` | Skip digests, press releases and self-promotion (default on) |
| `skipExpired` | Skip requests whose parsed deadline is already past |
| `languages` | e.g. `["en"]` |
| `searchQueries` | Extra free-text searches, e.g. `"looking for case studies"` |
| `maxRequests` | Cap per run |

### Output example

```json
{
  "topic": "Expert comment on sim-swap fraud",
  "outlet": "The Sun",
  "deadline": "23 Oct, 18:00",
  "deadlineDate": "2026-10-23",
  "deadlinePassed": false,
  "isUrgent": false,
  "requestCategory": "expert",
  "postType": "request",
  "requesterHandle": "thesourceradar.bsky.social",
  "requesterProfileUrl": "https://bsky.app/profile/thesourceradar.bsky.social",
  "isAggregator": true,
  "aggregatorName": "The Source Radar",
  "respondMethod": "link",
  "respondUrl": "https://www.thesourceradar.com/r/…",
  "respondEmail": null,
  "text": "Wanted right now: Expert comment on sim-swap fraud (The Sun). Deadline 23 Oct, 18:00. …",
  "hashtags": ["journorequest"],
  "matchedKeywords": [],
  "postedAt": "2026-10-22T16:30:29+00:00",
  "postUrl": "https://bsky.app/profile/thesourceradar.bsky.social/post/…"
}
```

Other fields: `requesterName`, `requesterDid`, `requesterAvatar`, `respondDmUrl`, `links`, `matchedHashtags`, `foundBy`, `language`, `likeCount`, `repostCount`, `replyCount`, `postUri`, `scrapedAt`. The `OUTPUT` record in the key-value store holds a run summary with counts of what was skipped and why.

Parsing is heuristic. Deadlines are found from phrases like "Deadline 31 Oct", "by Friday" or "12/23 8AM PT" and resolved relative to the post date. Outlet is detected from 200+ known titles and phrases like "for a national women's health mag". When nothing is found the field is `null`, and the full post `text` is always included.

### Pricing

Pay per event: **$0.003 per journalist request saved** ($3 per 1,000). Duplicates, already-seen requests (with `onlyNew`) and filtered posts are free. A typical daily scheduled run returns 3–10 new requests, so it costs **about $0.01–0.03 per day**. The run stops cleanly at your maximum total charge.

| Actor | Sources | Price |
|---|---|---|
| **This Actor** | Bluesky (public API) | $0.003 / request |
| fayoussef/journalist-request-finder | SourceBottle + others | $0.002 / request |

We deliberately do not use SourceBottle (its terms prohibit reproducing call-outs) or X/Twitter scraping.

### Use with AI agents / Apify MCP

The Actor works as a tool in the [Apify MCP server](https://mcp.apify.com) (Claude, Cursor, ChatGPT and other MCP clients). Example prompt: *"Run journalist-request-feed with keywords \['cybersecurity'] and onlyNew true, then draft a two-line pitch for each request."* The output is flat JSON with an explicit `respondMethod`, so agents can route each request to email, link or DM without further parsing.

### FAQ

**Why so few results?** Bluesky carries only a few call-outs a day. Use a longer `lookbackDays` (up to ~21 days is reachable) or remove keyword filters. An empty run on a quiet day costs nothing beyond the platform's small compute.

**How does "Only new" work?** Each request saved is remembered (post URI + text fingerprint) in the named key-value store `journalist-request-feed-state` under `SEEN-<stateName>` for 90 days. Use a separate `stateName` per schedule.

**Do I need a Bluesky account?** No.

**Is this allowed?** It reads public posts through Bluesky's official API, follows the [Bluesky developer guidelines](https://docs.bsky.app/docs/support/developer-guidelines) (paced requests, descriptive User-Agent, back-off on rate limits), and skips accounts that opted out of logged-out visibility (`!no-unauthenticated`). It is read-only and never posts, likes or messages. Please reply to journalists only with relevant pitches.

**Why is a post marked as a digest/promo?** The classifier uses wording patterns (round-ups, "expert available", press releases). Set `skipNonRequests` to false to see everything with its `postType`.

# Actor input Schema

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

Bluesky hashtags journalists use for source call-outs, without or with the <code>#</code>. Each hashtag returns the latest ~100 posts per run. Defaults cover the tags with real volume: journorequest, journorequests, prrequest, mediarequest, seekingsources, journalistrequest.

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

Optional. Keep only requests whose text, outlet or requester mentions at least one of these words or phrases (case-insensitive), e.g. <code>finance</code>, <code>mortgage</code>, <code>nutritionist</code>, <code>Guardian</code>. Leave empty to get every request. Matches appear in <code>matchedKeywords</code>.

## `excludeKeywords` (type: `array`):

Optional. Drop requests that mention any of these words or phrases (case-insensitive), e.g. <code>gift guide</code>, <code>celebrity</code>. Dropped posts are not charged.

## `lookbackDays` (type: `integer`):

Only return requests posted in the last N days (1–60). Anonymous Bluesky search returns the newest ~100 posts per hashtag, which for #JournoRequest covers about 3 weeks.

## `onlyNew` (type: `boolean`):

Skip requests already returned by an earlier run with the same state name. Turn this on for a daily/hourly schedule so you only get (and pay for) fresh call-outs. Seen requests are remembered for 90 days in the named key-value store <code>journalist-request-feed-state</code>.

## `stateName` (type: `string`):

Name of the dedupe memory used by "Only new requests". Use a different name per schedule or client (e.g. <code>finance-alerts</code>) to keep their histories separate. Letters, digits and - \_ . only.

## `includeAggregators` (type: `boolean`):

Include call-outs re-posted by request aggregators (The Source Radar, Media Matchmaker, Synapse). Their respond link points to the aggregator page. Turn off to keep only journalists posting directly.

## `skipNonRequests` (type: `boolean`):

Posts that use the hashtags but are not a source request (round-up digests, press releases, "expert available" pitches, spam) are detected heuristically and skipped without charge. Turn off to see everything; the type is in <code>postType</code>.

## `skipExpired` (type: `boolean`):

When a deadline date could be parsed and it is before today (UTC), skip the request. Requests without a detectable deadline are always kept.

## `includeReplies` (type: `boolean`):

Include posts that are replies in a thread. Off by default because replies under call-outs are mostly comments, not new requests.

## `languages` (type: `array`):

Optional. Keep only posts tagged with these language codes, e.g. <code>en</code>, <code>de</code>. Posts without a language tag are kept. Leave empty for all languages.

## `searchQueries` (type: `array`):

Optional free-text Bluesky searches added to the hashtags, e.g. <code>"looking for case studies"</code> or <code>#PRopportunity</code>. Results go through the same parsing, filters and dedupe.

## `maxRequests` (type: `integer`):

Stop after saving this many requests (1–5000). Each saved request is one billable event; the run also stops gracefully at your maximum total charge.

## Actor input object example

```json
{
  "hashtags": [
    "journorequest",
    "journorequests",
    "prrequest",
    "mediarequest",
    "seekingsources",
    "journalistrequest"
  ],
  "lookbackDays": 14,
  "onlyNew": false,
  "stateName": "default",
  "includeAggregators": true,
  "skipNonRequests": true,
  "skipExpired": false,
  "includeReplies": false,
  "maxRequests": 100
}
```

# Actor output Schema

## `requests` (type: `string`):

No description

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

No description

## `summary` (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 = {
    "hashtags": [
        "journorequest",
        "journorequests",
        "prrequest",
        "mediarequest",
        "seekingsources",
        "journalistrequest"
    ],
    "lookbackDays": 14,
    "maxRequests": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/journalist-request-feed").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 = {
    "hashtags": [
        "journorequest",
        "journorequests",
        "prrequest",
        "mediarequest",
        "seekingsources",
        "journalistrequest",
    ],
    "lookbackDays": 14,
    "maxRequests": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/journalist-request-feed").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 '{
  "hashtags": [
    "journorequest",
    "journorequests",
    "prrequest",
    "mediarequest",
    "seekingsources",
    "journalistrequest"
  ],
  "lookbackDays": 14,
  "maxRequests": 100
}' |
apify call gazidev/journalist-request-feed --silent --output-dataset

```

## MCP server setup

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

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/7ptWF31dR7kuFUxZa/builds/3kqAjkscYZbiTdo4N/openapi.json
