# DEV.to Scraper (`dami_studio/devto-scraper`) Actor

DEV.to scraper for articles by tag, author or top reactions. Each row has the title, summary, author, tags, reactions, comments, reading time, cover image and link, plus the full article text when you switch it on. $2.00 per 1,000 articles.

- **URL**: https://apify.com/dami_studio/devto-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** News, Developer tools, Integrations
- **Features:** Limited permissions
- **Stats:** 4 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.94 / 1,000 articles

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

**Scrape DEV.to articles by tag, author or reactions**: pick a tag, an author, or neither, and get
one row per article with the title, summary, tags, reaction and comment counts, reading time, cover
image and the link. Nothing to sign up for, and no API key.

The article body is off by default. You get the summary DEV shows in a card, and you switch on
**Include full body** when you want the whole post, which costs a second request per article.

| | |
|---|---|
| **Input** | A tag, an author username, a sort order, or any combination DEV accepts |
| **Output** | One row per article: title, summary, author, tags, reactions, comments, reading time, cover image, link. The full text on request |
| **Ceiling** | 1,000 articles per run |
| **Account needed** | None, and no API key |
| **Price** | $2.00 per 1,000 articles on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 2,500 |

### 🔍 What DEV.to Scraper does

It pages through DEV's article listing, 100 at a time, until it has the number you asked for or the
results run out. Duplicate article ids are dropped.

Three orders are available. **Top** returns the most-reacted articles published in the last
`topDays`, which is the one to use for finding what actually landed. **Latest** is newest first.
**Rising** is what DEV currently considers gaining traction, across the whole site.

Filter by `tag` or by `username`, or by neither for the site-wide list. Not both at once: DEV reads
only the tag when an author is set too, so that input is refused instead of charging you for other
people's articles. An author goes with **Latest**, because DEV lists one author's articles newest
first and ignores Top and Rising for them.

### 📋 What data you get from each DEV.to article

| What you get | Field |
|---|---|
| A stable id, the title and the link | `id`, `title`, `url` |
| The card summary DEV shows in a listing | `description` |
| Who wrote it | `author`, `authorUsername` |
| Every tag on the article | `tags` |
| Reactions, comments and reading time | `reactions`, `comments`, `readingTimeMinutes` |
| Cover image and publication time | `coverImage`, `publishedAt` |
| The whole article, when you switch it on | `body` |

### ▶️ How to scrape DEV.to articles

1. Open [DEV.to Scraper](https://apify.com/dami_studio/devto-scraper) and click **Try for free**.
2. Put a tag into **Tag**, or an author into **Author username**, or leave both empty.
3. Pick a **Sort by**. On `top`, set **Top days** to the window you care about. For an author, pick
   `latest`.
4. Set **Max articles**, tick **Include full body** if you want the text, then click **Start**.
5. Download the dataset as JSON, CSV or Excel, or read it from the Apify API.

### 💰 How much does it cost to scrape DEV.to articles?

**$2.00 per 1,000 articles**, which is $0.002 each on the Free plan. Paid plans pay a little less: $1.98 on Starter, $1.96 on Scale and $1.94 on Business. On
the free plan, the $5 Apify gives you each month covers about 2,500 articles.

You pay per article row delivered. Duplicates are dropped before they reach you, diagnostic rows are
not charged, and if nothing matches, no rows are charged. Articles below your minimum are left out
and not charged either.

One thing to know before you turn `includeBody` on: **an article whose body request did not come
back still arrives as a row, with `body` set to `null`, and still counts.** Check for nulls if you
are relying on the text.

### 📥 What you give it

```json
{
  "tag": "javascript",
  "sortBy": "top",
  "topDays": 7,
  "maxItems": 50,
  "includeBody": false
}
```

| Field | Default | What it is |
|---|---|---|
| `tag` | box starts at `javascript` | One DEV tag, like `react` or `webdev`. Case does not matter, it is lowercased for you. Leave empty for no tag filter. |
| `username` | none | One DEV author username, like `ben`. Leave empty for no author filter. Use it with `sortBy` set to `latest` and no `tag`; other combinations are refused, since DEV ignores them. |
| `sortBy` | `top` | `top`, `latest` or `rising`. `rising` only works with no tag and no author. |
| `topDays` | `7` | With `top`, how far back to look, 1 to 3,650 days. Ignored on the other two orders. |
| `minReactions` | none | Keep only articles with at least this many reactions. DEV has no such filter, so the run reads its pages and keeps the ones that pass. Articles left out are not charged. |
| `minComments` | none | The same for comments. Set both and an article has to clear both. |
| `maxItems` | `50` | Articles to return, up to 1,000. |
| `includeBody` | `false` | Fetch the whole article text as well. One extra request per article, so a big run takes noticeably longer. |
| `notionConnector` | none | Optional. Write every article into your own Notion as well as the dataset. |
| `notionParentId` | none | Optional. The Notion data source ID to write into. |
| `proxyConfiguration` | off | Optional, and off by default because a normal run does not need it. |

**For an author, set `sortBy` to `latest`.** DEV lists one author's articles newest first, whatever
their age, and ignores the Top window. Left on the default `top`, the run stops with a free
`BAD_INPUT` row rather than charge you for articles outside the window you asked for.

**A strict minimum can return fewer rows than `maxItems`.** The run reads at most five pages more
than it would without one, then stops with what passed.

### 📤 What you get back

A real row from a run in September 2026:

```json
{
  "ok": true,
  "id": 4602684,
  "title": "So You've Got a Technical Interview in 7 days...",
  "description": "So you got head-hunted. A recruiter from a large company wanted to talk to you about a React...",
  "url": "https://dev.to/cathylai/so-youve-got-a-technical-interview-in-7-days-22g5",
  "author": "Cathy Lai",
  "authorUsername": "cathylai",
  "tags": ["react", "career", "javascript"],
  "reactions": 9,
  "comments": 2,
  "readingTimeMinutes": 2,
  "coverImage": null,
  "publishedAt": "2026-09-09T05:18:13Z"
}
```

| Field | How to read it |
|---|---|
| `id` | DEV's own article id. Stable, so use it to dedupe across runs. |
| `description` | The card summary DEV shows in a listing, not the article. |
| `body` | Present **only** when `includeBody` is on. The article as markdown or HTML. `null` when that second request did not come back. |
| `tags` | Every tag on the article, not just the one you filtered by. |
| `reactions`, `comments`, `readingTimeMinutes` | Counts as DEV reports them. A missing value arrives as `0` rather than `null`, so a genuine zero and a missing field look the same. |
| `coverImage` | The header image URL, or `null` when the author set none. |
| `publishedAt` | A real ISO timestamp, or `null` when DEV did not give one. |
| `url`, `authorUsername` | Empty strings rather than `null` when DEV omits them, which is rare. |

### 🧾 Reading the output

Two kinds of row can land in your dataset.

| Row | How to spot it | Charged |
|---|---|---|
| An article | `ok: true` and an `id` | yes |
| A diagnostic | `ok: false` and an `errorCode` | no |

Filter on `ok == true` and you have your articles.

| Code | What it means |
|---|---|
| `NO_RESULTS` | Nothing matched that tag, author, order and window, or nothing read met your minimum. Widen `topDays`, lower the minimum or drop a filter. |
| `BAD_INPUT` | The input combines filters DEV ignores together, such as a tag with an author, or a minimum is not a whole number of 0 or more. Nothing was fetched or charged; the row's `note` says what to change. |
| `NOT_FOUND` | DEV answered 404 for the listing asked for. Check the username spelling. |
| `RATE_LIMITED` | DEV asked for a slower pace. Re-run with a smaller `maxItems`. |
| `SERVER_ERROR` | DEV answered with a server error. Usually passes on its own. |
| `NOT_SERVED` | This article was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here, and runs from 13 to 17 October wrote `NETWORK`. |
| `NETWORK` | DEV could not be reached. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The articles delivered before that are charged as usual. Give the run more time, or ask for fewer articles, to get the rest. |

A run that ends on one of these rows still finishes as succeeded, including when the time limit
stopped it before any article came back: the `TIME_LIMIT` row and an amber status message say so.

The diagnostic row echoes the `tag`, `username`, `sortBy` and `topDays` you ran with, which makes it
obvious when an empty result is a filter problem rather than an outage.

**The default table view hides `publishedAt` and `body`.** Switch to **All fields** or export as
JSON when you need either.

### 💡 What people use it for

- Finding what actually resonated in a tag this week, with `sortBy` on `top` and `topDays` at 7.
- Pulling an author's whole catalogue for a reading list or a newsletter.
- Watching a niche tag on a schedule with `minReactions` set, so only what clears your bar arrives.
  Dedupe on `id` and each run adds only what is new.

### 🚧 What it does not do

- **No body by default.** Turn `includeBody` on and expect the run to take longer.
- **No comments text.** You get the count, not what anybody wrote.
- **No reaction breakdown.** One total, not hearts against unicorns against bookmarks.
- **No series, no organisation feeds, no followers, no reading lists.**
- **`latest` with a tag is not sorted by date.** DEV ignores the newest-first request when a tag is
  set and sends the tag's current listing in its own order. On a busy tag that is roughly the last
  day of articles. Sort the rows on `publishedAt` if the order matters. With an author, `latest` is
  newest first.
- **No tag and author together, and no Rising for a tag or an author.** DEV's listing reads only one
  of them, so those inputs are refused, free.
- **A missing count reads as `0`.** There is no way to tell a genuine zero from a field DEV left out.
- **Nothing older than the window on `top`.** `topDays` is the whole search space for that order.
- **1,000 articles per run.** For more, narrow by tag and run several times.
- It is not an official DEV tool. Dami's Studio is independent and is not affiliated with or endorsed by DEV, or by any other company named on this page.

### 🧭 Which developer scraper do you need?

| If you want | Use |
|---|---|
| Articles from DEV.to by tag or author, with the full text on request | This one |
| Hacker News stories, Ask and Show HN posts, and comments | [Hacker News Scraper](https://apify.com/dami_studio/hacker-news-scraper) |
| Stack Overflow questions with the body and the accepted answer | [Stack Overflow Scraper](https://apify.com/dami_studio/stackexchange-scraper) |
| Repositories and developer profiles from a GitHub search | [GitHub Scraper](https://apify.com/dami_studio/github-scraper) |
| npm and PyPI package details and download counts | [npm + PyPI Package Scraper](https://apify.com/dami_studio/package-registry-scraper) |
| Product launches and their upvotes | [Product Hunt Scraper](https://apify.com/dami_studio/producthunt-scraper) |
| Research preprints with full abstracts and PDF links | [arXiv Scraper](https://apify.com/dami_studio/arxiv-scraper) |

### ❓ Questions people ask

#### Do I need a DEV account or an API key?

No. Everything it reads is public.

#### Why was my DEV author run refused?

DEV ignores Top and Rising for an author, and ignores the author when a tag is set too. Clear the
tag and set `sortBy` to `latest`.

#### How do I get the full article text?

Tick **Include full body**. It adds one request per article.

#### Can I filter by more than one DEV tag?

Not in one run. DEV's listing takes a single tag, so run it once per tag and join the rows on `id`.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/devto-scraper/api/python) has code for Python, JavaScript and the CLI. MCP
clients like Claude and ChatGPT use `https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/devto-scraper`;
coding assistants can load [our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-social-communities-SKILL.md). All
three bill your Apify account at the same price.

#### Is it legal to scrape DEV.to?

These are public articles. Author names are personal data, which GDPR and similar laws cover, so
have a reason for collecting them, and check DEV's terms for reuse of the text. Apify's write-up on
[the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting
point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the run ID and the tag, username and sort you used.
The `errorCode` on the diagnostic row usually names the problem on its own.

# Actor input Schema

## `tag` (type: `string`):

Only return articles with this DEV.to tag (e.g. "javascript", "react", "webdev"). Leave empty to not filter by tag.

## `username` (type: `string`):

Only return articles by this DEV.to author username (e.g. "ben"). Leave empty to not filter by author. Use it with Sort by = Latest and no tag: DEV ignores the author when a tag is set, and ignores Top and Rising for an author, so those inputs are refused before anything is fetched.

## `topDays` (type: `integer`):

When Sort by = Top, return the highest-reaction articles published within the last this many days (e.g. 7 = top of the past week, 365 = top of the past year). Ignored for Latest/Rising.

## `sortBy` (type: `string`):

How to order/select articles. "top" = most reactions in the last N days (see Top days), "latest" = newest first, "rising" = currently rising. Rising works only with no tag and no author. For an author, use Latest. With a tag, Latest gives DEV's current listing for that tag, not a strict date order.

## `minReactions` (type: `integer`):

Only return articles with at least this many reactions. Leave empty for no minimum. DEV has no such filter, so the run reads its pages and keeps the articles that pass; the rest are not charged. A strict minimum can return fewer articles than Max articles, because a run reads at most 5 pages more than it would without one.

## `minComments` (type: `integer`):

Only return articles with at least this many comments. Leave empty for no minimum. Works like Minimum reactions, and the two can be combined.

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

Maximum number of articles to return. The actor paginates the API (100 per page) until this many are collected or the results run out.

## `includeBody` (type: `boolean`):

Fetch the full article body (markdown/HTML) for each result via a second API call per article. Slower and makes more requests.

## `notionConnector` (type: `string`):

Optional. Write each article as a page into your Notion when the run finishes. Authorize a Notion connector once in Settings → API & Integrations → MCP connectors, then pick it here. Leave empty to skip (default), results are always saved to the dataset regardless.

## `notionParentId` (type: `string`):

Optional. The Notion data source ID of the database to write into (only used if a Notion connector is set). Leave empty to create the pages privately in your workspace instead.

## `proxyConfiguration` (type: `object`):

Optional, and not needed for a normal run.

## Actor input object example

```json
{
  "tag": "javascript",
  "topDays": 7,
  "sortBy": "top",
  "maxItems": 50,
  "includeBody": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Scraped rows are stored in the default dataset (one row per result). Blocked/empty/error runs return a single uncharged diagnostic row instead.

# 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 = {
    "tag": "javascript"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/devto-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 = { "tag": "javascript" }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/devto-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 '{
  "tag": "javascript"
}' |
apify call dami_studio/devto-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/devto-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/ME5MxjwzYdV0z8aFX/builds/K2UcCFhDyXOC5hxPC/openapi.json
