Tutorials
Medium profile in JavaScript
One authenticated call to /api/v1/medium/profile?handle=markmanson, the response it returned, and the line that reads one field out of it.
What you need
Node 18 or later, where fetch is global. No package to install. The same code runs in a browser, except that a browser would expose your key — call this from your server.
The call
const url = "https://honesthook.com/api/v1/medium/profile?handle=markmanson";
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.HH_KEY}` },
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const envelope = await response.json();
console.log(envelope.success, envelope.platform, envelope.endpoint);
What came back
{
"success": true,
"platform": "medium",
"endpoint": "medium/profile",
"data": {
"author": {
"id": "db77b01f3f54",
"handle": "markmanson",
"name": "Mark Manson",
"followers": 83742,
"following": 217,
"posts_count": null,
"verified": null,
"is_private": null
}
},
"error": null,
"cached": false
}
Recorded response, captured 27 Sep 2026. A live call returns the value at the moment you ask, not this one.
Every field in that capture
| In JavaScript | Type in this capture |
|---|
envelope.success | boolean |
envelope.platform | string |
envelope.endpoint | string |
envelope.data.author.id | string |
envelope.data.author.handle | string |
envelope.data.author.name | string |
envelope.data.author.followers | number |
envelope.data.author.following | number |
envelope.data.author.posts_count | null |
envelope.data.author.verified | null |
envelope.data.author.is_private | null |
envelope.error | null |
envelope.cached | boolean |
A field we cannot read comes back null, never 0. A zero would be a claim about the account; null is a fact about our collection. The full list of what this route does not return is on medium-api/profile.
Reading one field
console.log(envelope.data.author.followers);
envelope is the object from the block above. Keep the key on the server: a key shipped to a browser is a key anyone can read.
Medium has a trap that catches almost every first attempt: a handle that does not exist returns 200. Not a 404 — a successful response carrying a not-found page.
So any code that checks the status code and then parses is going to treat a typo as a real account. It will not crash. It will produce a profile-shaped nothing.
The anchor, not the status code
This endpoint decides whether the read worked by looking for the profile object itself in the page's Apollo state. If that object is not there, the answer is an error, regardless of what the status code said. The number also lives in that state rather than in the visible markup, which is a second reason not to read Medium with a pattern match over HTML.
That is the whole value of the route: the status code is not evidence here, and the thing that is evidence is not in the page text.
What you are calling
One authenticated GET to our endpoint, with your key in an Authorization header. The handle in the example is the one that produced the recorded response below.
Before you copy the block
Keep the key in an environment variable — all three examples read HH_KEY.
Then read the field list. There is no reliable public story count, so that field is null rather than a count of whatever happened to be rendered on the page.
The endpoint itself
Parameters, the handle format, what the route does not return and what a call costs: medium-api/profile.
The same call in curl · Python.