DEV Community

Ankit Gaur
Ankit Gaur

Posted on

Publishing to dev.to Programmatically in 2026: What Actually Works

Generated with Claude (Anthropic) based on a real session with @ankitg12.

Tags: #claude #ai #aiassisted


This is the second post in a two-part series. The first post covers getting Unix tools (grep, ls -lt, sed, awk) working in PowerShell — and cost ~102,000 tokens to figure out. Once the post was written, the natural next question was: can we skip the browser and publish it programmatically?

Turns out — yes, but not how you'd expect. Here's what we tried and what actually worked.


Attempt 1: Medium API — Dead on Arrival

Medium was the first choice. They used to have a clean REST API. Not anymore.

"Medium will not be issuing any new integration tokens for our API and will not allow any new integrations."

The GitHub repo was archived in March 2023. Existing tokens still work — but if you don't already have one, you can't get one. A paid membership doesn't help either.

Workaround: Publish to dev.to first, then use Medium's Import a Story tool to pull the URL in. Medium even sets the canonical URL back to dev.to, which is good for SEO.


Attempt 2: devto-cli — Almost

devto-cli by sinedied is the right idea — a purpose-built npm CLI for publishing markdown files to dev.to:

npm install -g @sinedied/devto-cli
dev push article.md
Enter fullscreen mode Exit fullscreen mode

Hit this immediately:

No GitHub repository provided.
Use --repo option or .env file to provide one.
Enter fullscreen mode Exit fullscreen mode

devto-cli requires a GitHub repo because it rewrites relative image URLs to point to raw GitHub content. Smart for image hosting — but unnecessary overhead for a text-only post with no images.

Could pass --repo username/repo and move on, but at this point it felt like the tool was doing more than needed.


Attempt 3: curl — Works, No Ceremony

dev.to has a straightforward REST API. The API key is still alive and well (unlike Medium's). A minimal test first:

curl -s -w "\nHTTP:%{http_code}" \
  -X POST https://dev.to/api/articles \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"article":{"title":"test","body_markdown":"hello","published":false}}'
Enter fullscreen mode Exit fullscreen mode

Got HTTP:201. No firewall, no issues.

For the real article, build the JSON payload with Python (to handle escaping cleanly) and pass it via --data @file:

import json

with open("article.md", encoding="utf-8") as f:
    content = f.read()

# Strip H1 title — dev.to uses the title field separately
body = "\n".join(content.split("\n")[2:]).strip()

payload = {
    "article": {
        "title": "Your Article Title",
        "body_markdown": body,
        "published": False,   # draft first
        "tags": ["powershell", "windows", "tutorial", "ai"]
    }
}

with open("payload.json", "w", encoding="utf-8") as f:
    json.dump(payload, f)
Enter fullscreen mode Exit fullscreen mode

Then publish:

curl -s -X POST https://dev.to/api/articles \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data @payload.json
Enter fullscreen mode Exit fullscreen mode

To update an existing draft (get the article ID from /api/articles/me/unpublished):

# Get draft IDs
curl -s -H "api-key: YOUR_API_KEY" \
  https://dev.to/api/articles/me/unpublished \
  | python3 -c "
import sys, json
for a in json.load(sys.stdin):
    print(a['id'], a['title'])
"

# Update draft
curl -s -X PUT https://dev.to/api/articles/ARTICLE_ID \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data @payload.json
Enter fullscreen mode Exit fullscreen mode

One Gotcha: Draft Preview URLs

The URL returned by the API is not directly accessible:

https://dev.to/username/article-slug-temp-slug-XXXX
Enter fullscreen mode Exit fullscreen mode

To preview a draft, go to dev.to/dashboard, open the draft, and dev.to generates a preview URL with a token:

https://dev.to/username/slug?preview=<long_token>
Enter fullscreen mode Exit fullscreen mode

This is expected behavior — drafts are private until published.


The Full Flow

Write markdown locally
       ↓
python3 build_payload.py       # build payload.json
       ↓
curl --data @payload.json      # POST to dev.to (draft)
       ↓
Review on dev.to dashboard
       ↓
Hit Publish
       ↓
Import URL into Medium         # medium.com/p/import
Enter fullscreen mode Exit fullscreen mode

Summary

Method Status Notes
Medium API Dead No new tokens since 2023
devto-cli Works Needs GitHub repo even for no-image posts
curl + Python payload Works Simplest, no dependencies
Python urllib 403 SSL/TLS difference vs curl on Windows
Medium Import tool Works Pull from dev.to URL after publishing

Further Reading

Top comments (0)