Home / Articles / Blog

Medium API automation
APIMedium AutomationAIGenAI

Medium API automation

Working with medium API

Shrey Vijayvargiya

Hello and welcome to the new blog

In today's story, we will be building a simple API automation posting tool that posts our website content on my specific Medium and Devto platforms.

This automation saves me tonnes of copy-pasting time, and AI integration can even help me rewrite the content specifically for each of the platforms.

For posting content on Medium, I am using the medium api and the same for the devto.

One API to post on a medium automation tool, but few things we have to cover before actually using the API.

First thing is to have a medium API token or bearer token or auth token or a JWT token all are the same in opinion.

Go to the settings page on the medium and grab the Integration tokens.

Store it inside the ENV file.

Secondly, we will be using the integration token as an authorisation token to grab the user id using GET request to the https://api.medium.com/v1/me endpoint.

text
const axios = require('axios');
let data = '';

let config = {
  method: 'get',
  url: 'https://api.medium.com/v1/me',
  headers: { 
    'Authorization': 'Bearer {add_your_token}'
  },
  data : data
};

axios.request(config)
.then((response) => {
  console.log(JSON.stringify(response.data));
})
.catch((error) => {
  console.log(error);
});

This endpoint will return the user ID that we will need to along with the integration token, to makea  POST request to another endpoint that is https://api.medium.com/v1/users/{userId}/posts

text
const axios = require('axios');
let data = JSON.stringify({
  "title": "Your Blog Title",
  "contentFormat": "html",
  "content": "<h1>Your Blog Title</h1><p>This is the blog body content</p>",
  "tags": [
    "tag1",
    "tag2"
  ],
  "publishStatus": "public"
});

let config = {
  method: 'post',
  url: 'https://api.medium.com/v1/users/11585d0546e2287a0f02365d2a2a2ae80916fe4982dac93cbb713ef19af1dfc16/posts',
  headers: { 
    'Content-Type': 'application/json', 
    'Authorization': 'Bearer {add_your_token}'
  },
  data : data
};

axios.request(config)
.then((response) => {
  console.log(JSON.stringify(response.data));
})
.catch((error) => {
  console.log(error);
});

In total, 3 steps, we can post any content to Medium.com

  1. Grab the user integration token from the account page
  2. Make a GET request to get your userId
  3. Make a POST request with a content body and the userId to post an article on Medium

Check the official github repo for more details, and today's story will be quite short.

The next part is testing the API to put real content on the post. I'll be using some Gemini AI model to create an endpoint that takes my prompt, generates AI content using an LLM model, and finally posts it to my Medium account.

I'll be using the @google/genai npm module and will be needing a Google GEMINI API key along with the Hono.js full-stack framework.

text
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import dotenv from "dotenv";
import { GoogleGenAI } from "@google/genai";

dotenv.config();

const genai = new GoogleGenAI({
	apiKey: process.env.GOOGLE_GENAI_API_KEY,
});

const app = new Hono();

app.post("/medium-automation", async (c) => {
	const { prompt } = await c.req.json();

	const response = await genai.models.generateContent({
		model: "gemini-2.0-flash",
		contents: [
			{
				role: "model",
				parts: [
					{
						text: "You are a helpful assistant that can help me write a blog post on Medium. You will be given a prompt and you will need to write a blog post on Medium.",
					},
				],
			},
			{
				role: "user",
				parts: [{ text: prompt }],
			},
		],
	});
	return c.text(response.candidates[0].content.parts[0].text);
});

const port = 3001;
console.log(`Server is running on port ${port}`);
serve({
	fetch: app.fetch,
	port,
});

The above endpoint will generate the content using the genai LLM model and in the next endpoint or just after the content, I can use the medium POST endpoint to post the content to medium.com and the only thing I need to take care is the body construction of the POST request, which I think any developer can DO or learn to DO.

text
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import dotenv from "dotenv";
import { GoogleGenAI } from "@google/genai";

dotenv.config();

const genai = new GoogleGenAI({
	apiKey: process.env.GOOGLE_GENAI_API_KEY,
});

const app = new Hono();


function convertMarkdownToHtml(markdown) {
	if (!markdown) return "";

	let html = markdown
		// Headers
		.replace(/^### (.*$)/gim, "<h3>$1</h3>")
		.replace(/^## (.*$)/gim, "<h2>$1</h2>")
		.replace(/^# (.*$)/gim, "<h1>$1</h1>")
		// Bold and italic
		.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
		.replace(/\*(.*?)\*/g, "<em>$1</em>")
		// Code blocks
		.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
		.replace(/`(.*?)`/g, "<code>$1</code>")
		// Links
		.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
		// Lists
		.replace(/^\* (.*$)/gim, "<li>$1</li>")
		.replace(/^- (.*$)/gim, "<li>$1</li>")
		// Paragraphs (convert double line breaks to paragraphs)
		.replace(/\n\n/g, "</p><p>")
		// Single line breaks
		.replace(/\n/g, "<br>");

	// Wrap in paragraph tags if not already wrapped
	if (!html.startsWith("<")) {
		html = `<p>${html}</p>`;
	}

	// Clean up empty paragraphs
	html = html.replace(/<p><\/p>/g, "");

	return html;
}

app.post("/medium-automation", async (c) => {
	const { prompt } = await c.req.json();

	// Validate input
	if (!prompt) {
		return c.json(
			{
				success: false,
				message: "Prompt is required",
			},
			400
		);
	}

	// Check if Medium token is available
	if (!process.env.MEDIUM_TOKEN) {
		return c.json(
			{
				success: false,
				message: "Medium API token not configured",
			},
			500
		);
	}

	const response = await genai.models.generateContent({
		model: "gemini-2.0-flash",
		contents: [
			{
				role: "model",
				parts: [
					{
						text: "You are a helpful assistant that can help me write a blog post on Medium. You will be given a prompt and you will need to write a blog post on Medium.",
					},
				],
			},
			{
				role: "user",
				parts: [{ text: prompt }],
			},
		],
	});

	const generatedContent = response.candidates[0].content.parts[0].text;

	const lines = generatedContent.split("\n");
	let title = "Untitled Blog";
	let contentBody = generatedContent;

	if (lines[0] && lines[0].startsWith("#")) {
		title = lines[0].replace(/^#+\s*/, "").trim();
		contentBody = lines.slice(1).join("\n").trim();
	}

	const htmlContent = convertMarkdownToHtml(contentBody);

	const postData = {
		title: title,
		contentFormat: "html",
		content: htmlContent,
		tags: ["AI", "automation", "blogging"],
		publishStatus: "public",
	};

	let data = JSON.stringify(postData);

	let config = {
		method: "post",
		url: "https://api.medium.com/v1/users/11585d0546e2287a0f02365d2a2a2ae80916fe4982dac93cbb713ef19af1dfc16/posts",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${process.env.MEDIUM_TOKEN}`,
		},
		data: data,
	};

	try {
		const mediumResponse = await fetch(config.url, {
			method: config.method,
			headers: config.headers,
			body: config.data,
		});

		// Get response as text first to check content type
		const responseText = await mediumResponse.text();

		let responseData;
		try {
			responseData = JSON.parse(responseText);
		} catch (parseError) {
			return c.json(
				{
					success: false,
					message: "Medium API returned invalid response",
					error:
						"Response was not valid JSON - likely authentication or endpoint issue",
					status: mediumResponse.status,
					responsePreview: responseText.substring(0, 200),
				},
				500
			);
		}

		if (mediumResponse.ok) {
			return c.json({
				success: true,
				message: "Post published successfully to Medium",
				data: responseData,
			});
		} else {
			return c.json(
				{
					success: false,
					message: "Failed to publish to Medium",
					error: responseData,
					status: mediumResponse.status,
				},
				mediumResponse.status
			);
		}
	} catch (error) {
		console.error("Error posting to Medium:", error.message);
		return c.json(
			{
				success: false,
				message: "Error posting to Medium",
				error: error.message,
			},
			500
		);
	}
});

const port = 3001;
console.log(`Server is running on port ${port}`);
serve({
	fetch: app.fetch,
	port,
});

In the above endpoint, I've added the createMarkdownToHtml method as AI sometimes provide content or response in the markdown format. 

We will keep this short. Do note medium API will have a rate limit and posting API limit, so if you are thinking of adding AI automation to post 10+ blogs within a minute, it will throw an error.

See you in the next one

iHateReading

External Links (8)

Explore topics

More in

Weekly letter

Subscribe to iHateReading

Our once-a-week newsletter on programming, jobs, AI, and building products online.

Our once a week newsletter on Programming, Jobs, AI, and Business