# Medium Article Scraper: Tags, Authors & Publications (`cliqtomedia/medium-articles-scraper`) Actor

Scrape the latest Medium stories by tag, author, publication or URL. Get title, author, date, tags, member-only flag, reading time and open-story text. No login.

- **URL**: https://apify.com/cliqtomedia/medium-articles-scraper.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Categories:** News, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.50 / 1,000 saved stories

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

Medium Article Scraper reads the latest Medium stories by tag, author, publication or story URL. It returns title, author, publication, date, tags, member-only flag, reading time and the full text of open stories. No login, cookies or API key. The example run saves 10 stories about AI in about 5 seconds and costs **$0.093** on the Free tier.

![Medium Article Scraper: tags, authors and publications to a table of stories with member-only flag, reading time and full text](https://api.apify.com/v2/key-value-stores/Vag4vYLQadf9tQirp/records/HERO-v1.png)

### Contents

- [What it does](#what-it-does)
- [Data you get](#data-you-get)
- [Quick start](#quick-start)
- [Input](#input)
- [Input examples](#input-examples)
- [Output](#output)
- [Pricing](#pricing)
- [Use cases](#use-cases)
- [Scheduled runs and only new stories](#scheduled-runs-and-only-new-stories)
- [API, export and integrations](#api-export-and-integrations)
- [Limits and partial results](#limits-and-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Support](#support)

### What it does

The Actor reads public [Medium RSS feeds](https://help.medium.com/hc/en-us/articles/214874118-Using-RSS-feeds-of-profiles-publications-and-topics). Choose one mode and give a list:

- **tag** — topic tags, such as `artificial-intelligence` or `Machine Learning`;
- **author** — usernames or profile links, such as `@ev` or `ev.medium.com`;
- **publication** — publication names or links, such as `towards-data-science`;
- **article** — story links. The Actor finds each story in its author or publication feed.

Every feed gives **up to 10 latest stories**. This is a Medium RSS limit: older stories and full archives are not available. One run reads up to 20 feeds and saves up to 200 stories.

#### Good fit

- Watch new stories on a topic, an author or a publication every day.
- Collect story metadata and open text for research, newsletters or AI tools.
- Check if a story is member-only before you share or analyze it.

#### Not a fit

- Full author archives or search by keyword. RSS gives only the latest 10 stories.
- Claps, responses, followers or view counts. RSS does not have them.
- Text of member-only stories. The Actor does not log in and does not bypass the paywall; you get a preview flag only.

### Data you get

Each row is one story: `id`, `url`, `title`, `subtitle`, author (`authorName`, `authorUsername`, `authorUrl`), publication (`publicationSlug`, `publicationName`, `publicationUrl`), `publishedAt`, `updatedAt`, `tags`, `isMemberOnly`, `contentAvailability`, `wordCount`, `readingTimeMin`, `imageUrl`, and with **includeContent** also `contentText` and `contentHtml`. Every row also says where it came from (`sourceType`, `sourceValue`, `feedUrl`) and when (`fetchedAt`).

The run report `RUN_SUMMARY` lists every feed with its status: `complete`, `empty`, `not_found`, `error` or `skipped`.

This Actor is independent. It is not made or endorsed by Medium. Stories belong to their authors; check the [Medium Terms of Service](https://policy.medium.com/medium-terms-of-service-9db0094a1e0f) before you reuse the text.

### Quick start

1. Open **Input**.
2. Keep the example: **Mode** `tag`, **Tags** `artificial-intelligence`, **Max items** `20`.
3. Click **Start**.
4. Open **Articles** in the output. Check `title`, `authorName`, `publishedAt` and `tags`.

Expected result: about 10 of the newest AI stories in 2–10 seconds. A tag feed has 10 stories, so `maxItems: 20` saves 10.

### Input

| Field | Type | Required | Default / example | Allowed values | What it does | Tip |
| --- | --- | --- | --- | --- | --- | --- |
| `mode` | string | yes | `tag` | `tag`, `author`, `publication`, `article` | Which list the run reads. Other lists are ignored. | One mode per run. Use several runs for a mix. |
| `tags` | array of strings | in `tag` mode | example `["artificial-intelligence"]` | 1–20 items; slug, name or `medium.com/tag/...` link | Reads `medium.com/feed/tag/<slug>`. `Machine Learning` becomes `machine-learning`. Non-Latin tags, such as `日本語`, work. | Names and links of the same tag count once. |
| `authors` | array of strings | in `author` mode | `["ev"]` | 1–20 items; `ev`, `@ev`, `medium.com/@ev`, `ev.medium.com` | Reads the author's feed. | Different spellings of one author count once. |
| `publications` | array of strings | in `publication` mode | `["towards-data-science"]` | 1–20 items; slug or `medium.com/<slug>` / `<slug>.medium.com` link | Reads the publication feed. | For a custom domain, use the Medium slug, such as `netflix-techblog`. |
| `articleUrls` | array of strings | in `article` mode | — | 1–100 links on `medium.com` or `*.medium.com`, from up to 20 different authors or publications | Finds each story in its author or publication feed. | Short `/p/<id>` links have no author: use the full story link. |
| `maxItems` | integer | no | `100` (example `20`) | 1–200 | Maximum saved rows for the whole run. Each row is charged. | Set it to the number of rows you want to pay for. |
| `includeContent` | boolean | no | `false` | `true`, `false` | Adds `contentText` and `contentHtml` of open stories, `wordCount` and `readingTimeMin`. | In `tag` mode the text comes from extra feeds; see [Limits](#limits-and-partial-results). |
| `onlyNew` | boolean | no | `false` | `true`, `false` | Skips stories that earlier runs with the same `stateKey` already saved. | Use it for scheduled runs. |
| `stateKey` | string | no | `default` | 1–40 letters, digits or `-` | Name of the memory for `onlyNew`. | Use one name per watch list. |

**Empty or wrong input.** An empty list for the chosen mode, an unknown field, a bad link or more than 20 feeds fails the run before any feed is read. A tag that has no stories gives status `empty`. An author or publication that does not exist gives status `not_found`. Both are normal results, not errors.

**Duplicates.** A story that appears in two feeds is saved once.

### Input examples

#### Latest stories for several tags

```json
{
  "mode": "tag",
  "tags": ["artificial-intelligence", "Machine Learning", "python"],
  "maxItems": 30
}
```

Expected: up to 30 rows, 10 per tag, newest first inside each tag. `isMemberOnly` is `null`, because a tag feed shows only a short snippet.

#### Publication stories with full text

```json
{
  "mode": "publication",
  "publications": ["towards-data-science", "netflix-techblog"],
  "includeContent": true,
  "maxItems": 20
}
```

Expected: up to 20 rows. Open stories have `contentAvailability: "full"`, `contentText` and `readingTimeMin`. Member-only stories have `isMemberOnly: true` and `contentAvailability: "preview_member_only"` with no text.

#### Daily watch list of authors

```json
{
  "mode": "author",
  "authors": ["@ev", "netflixtechblog.medium.com"],
  "onlyNew": true,
  "stateKey": "my-authors",
  "maxItems": 50
}
```

Expected: the first run saves the latest stories. Later runs save only stories that are new since the last run, often 0 rows.

#### Check specific stories

```json
{
  "mode": "article",
  "articleUrls": ["https://ev.medium.com/making-social-social-again-0126fa5c6ce8"],
  "includeContent": true
}
```

Expected: one row if the story is among the 10 latest of its author or publication. Otherwise `RUN_SUMMARY.articles` says `not_in_feed` or `feed_not_found`, and no row is charged.

### Output

#### Output fields

| Field | Type | Can be empty? | Example | Meaning |
| --- | --- | --- | --- | --- |
| `id` | string | no | `aac387e10cb6` | Medium story ID. |
| `url` | string | no | `https://medium.com/data-science/deep-dive-...-aac387e10cb6` | Story link without tracking parameters. |
| `title` | string | no | `Deep Dive into WebSockets ...` | Story title. |
| `subtitle` | string | yes | `One thing I have noticed ...` | Subtitle or snippet from the feed. |
| `authorName` | string | yes | `clara` | Author name shown on Medium. |
| `authorUsername` | string | yes | `ev` | Medium username, when the feed or link shows it. |
| `authorUrl` | string | yes | `https://medium.com/@ev` | Author profile link. |
| `publicationSlug` | string | yes | `towards-data-science` | Publication slug for publication stories. |
| `publicationName` | string | yes | `TDS Archive` | Publication name from the feed. |
| `publicationUrl` | string | yes | `https://medium.com/data-science` | Publication link. |
| `publishedAt` | string (ISO date) | yes | `2025-02-03T11:02:00.000Z` | Publish time. |
| `updatedAt` | string (ISO date) | yes | `2025-02-03T11:02:00.167Z` | Last update time from the feed. |
| `tags` | array of strings | no (can be `[]`) | `["websocket", "software-development"]` | Story tags. |
| `isMemberOnly` | boolean | yes | `false` | `true` — member-only, preview only; `false` — open story; `null` — unknown (tag feed without text). |
| `contentAvailability` | string | no | `full` | `full`, `preview_member_only`, `not_requested` or `unknown`. |
| `wordCount` | integer | yes | `1837` | Words in the open text. Only when the text is `full`. |
| `readingTimeMin` | number | yes | `7.6` | Estimated reading time in minutes (words ÷ 265 plus images). Only when the text is `full`. |
| `imageUrl` | string | yes | `https://cdn-images-1.medium.com/...jpeg` | First image of the story. |
| `contentText` | string | yes | `WebSockets are ...` | Plain text of an open story. Only with `includeContent`. |
| `contentHtml` | string | yes | `<p>WebSockets are ...</p>` | HTML of an open story. Only with `includeContent`. |
| `sourceType` | string | no | `publication` | Feed type: `tag`, `author` or `publication`. |
| `sourceValue` | string | no | `towards-data-science` | Your input value after cleanup. |
| `feedUrl` | string | no | `https://medium.com/feed/towards-data-science` | Feed that gave the row. |
| `fetchedAt` | string (ISO date) | no | `2026-10-15T00:20:38.167Z` | When the Actor read the feed. |

`null` means "not in the source" or "not known". It never means 0.

#### Output example

Trimmed: `contentText` and `contentHtml` are shortened.

```json
{
  "id": "aac387e10cb6",
  "url": "https://medium.com/data-science/deep-dive-into-websockets-and-their-role-in-client-server-communication-aac387e10cb6",
  "title": "Deep Dive into WebSockets and Their Role in Client-Server Communication",
  "subtitle": null,
  "authorName": "clara",
  "authorUsername": null,
  "authorUrl": null,
  "publicationSlug": "towards-data-science",
  "publicationName": "TDS Archive",
  "publicationUrl": "https://medium.com/data-science",
  "publishedAt": "2025-02-03T11:02:00.000Z",
  "updatedAt": "2025-02-03T11:02:00.167Z",
  "tags": ["software-development", "system-design-concepts", "websocket", "client-server-model", "getting-started"],
  "isMemberOnly": false,
  "contentAvailability": "full",
  "wordCount": 1837,
  "readingTimeMin": 7.6,
  "imageUrl": "https://cdn-images-1.medium.com/max/1024/1*HzsgZYLeeRwRNm5gbwQWrg.jpeg",
  "contentText": "…",
  "contentHtml": "…",
  "sourceType": "publication",
  "sourceValue": "towards-data-science",
  "feedUrl": "https://medium.com/feed/towards-data-science",
  "fetchedAt": "2026-10-15T00:20:38.167Z"
}
```

#### Where to find results

| Situation | Where | What you see |
| --- | --- | --- |
| Normal run | Dataset views **Articles** and **Content** | One row per story. |
| No stories (empty tag, unknown author) | Dataset is empty; `RUN_SUMMARY` | Feed status `empty` or `not_found`, run `SUCCEEDED`. |
| Stopped early (Medium limit, time or budget) | Dataset and `RUN_SUMMARY` | Saved rows stay. `status: "partial"` and `errorCode` explain why. |
| Wrong input or feed failure | Run status and `RUN_SUMMARY` | `FAILED` with `errorCode` and a short message. |

### Pricing

This Actor uses pay per event. You pay for:

| Event | Free | Bronze | Silver | Gold and higher |
| --- | ---: | ---: | ---: | ---: |
| Actor start (once per run) | $0.003 | $0.003 | $0.003 | $0.003 |
| Saved story (one Dataset row) | $0.009 | $0.0085 | $0.008 | $0.0075 |

Platform usage is included. Stories that are skipped as duplicates, by `onlyNew`, or not found are not charged. A run with 0 saved stories costs only the start fee.

| Scenario | Input | Rows | Free tier price |
| --- | --- | ---: | ---: |
| Example run | 1 tag, `maxItems: 20` | 10 | $0.093 |
| 10 publications with text | 10 publications, `includeContent` | 100 | $0.903 |
| Biggest run | 20 tags, `maxItems: 200` | up to 200 | up to $1.803 |
| Nothing new | `onlyNew`, no new stories | 0 | $0.003 |

To cap the cost, set `maxItems` or a maximum charge for the run in Apify. When the cap is reached, the run stops with `partial` and `CHARGE_LIMIT` and keeps the saved rows.

### Use cases

| Goal | Input | Key fields | Next step |
| --- | --- | --- | --- |
| Topic monitoring | `tags` + `onlyNew` on a schedule | `title`, `url`, `publishedAt`, `tags` | Send new rows to Slack, email or a sheet. |
| Content research | `publications` + `includeContent` | `contentText`, `wordCount`, `readingTimeMin` | Feed open text to an LLM or a search index. |
| Author tracking | `authors` | `title`, `publishedAt`, `isMemberOnly` | Keep a list of new posts by people you follow. |
| Link check | `articleUrls` | `isMemberOnly`, `contentAvailability` | See if a story is open before you share it. |

### Scheduled runs and only new stories

Create a schedule in Apify (for example, every day). Turn on `onlyNew` and give the list a `stateKey`. The Actor keeps the IDs of stories it saved in a named key-value store `medium-articles-seen-<stateKey>`. The next run with the same `stateKey` skips them and charges only new rows. Rows from a run that stopped early are remembered too, so you never pay twice for one story.

A feed shows only 10 stories. For a busy tag (new stories every few minutes) run more often, or some stories will pass between runs.

### API, export and integrations

- **Export:** download the Dataset as JSON, CSV, Excel, XML or RSS from the run page.
- **API:** start the Actor with the [Apify API](https://docs.apify.com/api/v2) and read `/datasets/{id}/items?view=overview`.
- **Schedules and webhooks:** use Apify schedules for daily runs and webhooks or integrations (Make, Zapier, n8n) to send new rows on.

#### Use with AI agents (MCP)

- MCP server with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/medium-articles-scraper`.
- Smallest input: `{"mode": "tag", "tags": ["artificial-intelligence"], "maxItems": 10}`.
- Key output fields: `title`, `url`, `authorName`, `publishedAt`, `contentText` (only with `includeContent`, open stories).
- A typical run takes 2–10 seconds. 10 rows cost $0.093 on the Free tier.
- Limits: one mode per run, up to 20 feeds and 200 rows, 10 latest stories per feed.

### Limits and partial results

- **10 stories per feed.** Medium RSS has no paging. A run cannot go deeper than the 10 latest stories.
- **20 feeds per run.** Medium allows about 25 feed reads in a short time from one IP address. After that it answers HTTP 429 for 10 minutes or more. The Actor does not try to go around this limit.
- **Medium limit during a run.** If Medium answers 429, the Actor stops reading. It keeps the rows saved so far and ends `SUCCEEDED` with `status: "partial"` and `errorCode: "RATE_LIMITED"`. Feeds not read get `skipped`. If no row was saved, the run is `FAILED` with `RATE_LIMITED`. Runs that start at the same time can share this limit, so a big run right after another big run can stop early.
- **Text in tag mode.** A tag feed has only a snippet. With `includeContent`, the Actor reads the author or publication feed of each story to get the text, but only while the per-run request budget allows. Other rows keep `contentAvailability: "unknown"`. For reliable full text, use `author` or `publication` mode.
- **Member-only stories.** Only the preview. `isMemberOnly: true`, no text.
- **Time.** A run stops reading after 4 minutes (or 10 seconds before its timeout) and ends `partial` with `DEADLINE`.
- **Feed failure.** If a feed fails after retries, the run is `FAILED` with `SOURCE_ERROR`, but saved rows stay in the Dataset.

### Troubleshooting

#### The run saved fewer rows than `maxItems`

Symptom: 10 rows with `maxItems: 100`.
Reason: each feed gives at most 10 stories.
Action: add more tags, authors or publications (up to 20).

#### `partial` with `RATE_LIMITED`

Symptom: some feeds are `skipped` in `RUN_SUMMARY`.
Reason: Medium limited feed reads from the IP address.
Action: keep the saved rows. Start a new run with the skipped feeds after 15 minutes or more.

#### Status `not_found`

Symptom: no rows, feed status `not_found`.
Reason: the author or publication does not exist under that name, or a custom domain is used.
Action: open the profile on Medium and copy the username or the publication slug from the address.

#### `contentAvailability` is `unknown`

Reason: tag mode with `includeContent`, and the request budget ended before the author feed of this story was read.
Action: read the story's author or publication in a separate run.

### FAQ

#### Do I need a Medium account?

No. The Actor reads public RSS feeds. It does not log in and does not use cookies.

#### Can I get claps or responses?

No. Medium RSS does not include them, and the Actor reads only RSS.

#### Can I get older stories?

No. Each feed shows only the 10 latest stories. Use `onlyNew` on a schedule to build your own history over time.

#### Why is a story missing in article mode?

The story is not among the 10 latest of its author or publication feed. `RUN_SUMMARY.articles` shows `not_in_feed`.

#### Is the text of member-only stories included?

No. The Actor does not bypass the Medium paywall.

### Support

For help, open an issue on this Actor's **Issues** tab in Apify Store. Share the run ID and your input. Remove tokens and private information.

# Changelog

This Actor's version history is a separate document: https://apify.com/cliqtomedia/medium-articles-scraper/changelog.md

# Actor input Schema

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

Which list to read. Only the list for this mode is used.

## `tags` (type: `array`):

Tag slugs or names, such as artificial-intelligence or "Machine Learning", or medium.com/tag/... URLs. Each tag gives up to 10 latest stories. Up to 20 tags per run: Medium allows about 25 feed reads per IP.

## `authors` (type: `array`):

Usernames (ev or @ev) or profile URLs (medium.com/@ev, ev.medium.com). Each author gives up to 10 latest stories. Up to 20 per run.

## `publications` (type: `array`):

Publication slugs (towards-data-science, gitconnected) or medium.com URLs. For a custom domain, use the slug. Each gives up to 10 latest stories. Up to 20 per run.

## `articleUrls` (type: `array`):

Story URLs on medium.com or \*.medium.com. Found only if the story is among the 10 latest in its author or publication feed. Up to 20 different authors or publications per run.

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

Maximum rows for the whole run. Every row is a charged result.

## `includeContent` (type: `boolean`):

Add text and HTML of open stories. Member-only stories stay previews. In tag mode it also reads author or publication feeds while the per-run request budget allows; other rows keep contentAvailability unknown.

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

Skip stories that earlier runs with the same memory name already returned. For scheduled runs.

## `stateKey` (type: `string`):

Name of the memory used by "Only new articles". Use different names for different watch lists.

## Actor input object example

```json
{
  "mode": "tag",
  "tags": [
    "artificial-intelligence"
  ],
  "maxItems": 20,
  "includeContent": false,
  "onlyNew": false,
  "stateKey": "default"
}
```

# Actor output Schema

## `articles` (type: `string`):

Article table.

## `content` (type: `string`):

Text of open stories (with includeContent).

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

Status, feeds read, not found feeds and articles, limits.

# 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": "tag",
    "tags": [
        "artificial-intelligence"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/medium-articles-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": "tag",
    "tags": ["artificial-intelligence"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/medium-articles-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": "tag",
  "tags": [
    "artificial-intelligence"
  ],
  "maxItems": 20
}' |
apify call cliqtomedia/medium-articles-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/medium-articles-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/QSstFXJZohkFrr6bQ/builds/w97SdPDNrhAjzxaFT/openapi.json
