REST API reference
Medium API
13 endpoints for Medium. Each one is a POST with a JSON body, called with your API key.
- Base URL
- https://cotlet.dev/api/v1/medium
- Connects with
- Your own Medium account (writer or editor role in a publication to submit or schedule there)
- Integration page
- Medium
| Endpoint | Access | Description |
|---|---|---|
| list_stories | read | Your stories, newest first, with status, publication, tags and URL. |
| get_story | read | Full story: title, subtitle, body as Markdown, tags, canonical link, paywall and dates. |
| get_story_stats | read | Views, reads, member read ratio, claps, responses and earnings for paywalled stories. |
| get_account_stats | read | The account-level figures Medium shows on your stats page, for one month. |
| list_responses | read | Responses to a story, with author, text and whether you replied. |
| list_publications | read | Publications you own, edit or write for, with your role. |
| create_draft | write | New draft from Markdown. Nothing is public until you publish or schedule it. |
| import_story | write | Medium's "Import a story": copies a post from a URL into a draft, backdated, with a canonical link. |
| update_story | write | Change the title, subtitle, body, tags, canonical link or paywall of a draft or a published story. |
| publish_story | write | Publish a draft now. |
| schedule_story | write | Publish a draft at a set time. |
| submit_to_publication | write | Send a draft to the editors of a publication you write for. |
| reply_to_response | write | Reply as you under a response to your story. |
List stories
readPOST https://cotlet.dev/api/v1/medium/list_stories
Lists your own stories, newest first. Filter by status to find drafts or scheduled stories. Each item has the story_id used by the other tools. Pages hold up to 50 stories; pass next_cursor back as cursor for the next page.
Parameters
| Name | Type | Description |
|---|---|---|
| status | enum | published, draft or scheduled. Default: published. |
| limit | integer | 1 to 50. Default: 20. |
| cursor | string | Next page cursor from the previous call. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/list_stories \
-H "Authorization: Bearer $COTLET_API_KEY" result = client.call("medium", "list_stories") const result = await cotlet.call('medium', 'list_stories'); cotlet call medium list_stories Example response
{
"stories": [
{
"story_id": "3f9a1c27b84e",
"title": "Shipping small",
"status": "published",
"publication": null,
"tags": [
"Python",
"Programming"
],
"published_at": "2026-10-02T09:00:00-04:00",
"url": "https://medium.com/@yourname/shipping-small-3f9a1c27b84e"
}
],
"next_cursor": null
} Get story
readPOST https://cotlet.dev/api/v1/medium/get_story
Returns one story with its body as Markdown. Use it before update_story so your agent changes only the part you asked for. Works for drafts, scheduled and published stories.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/get_story \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"story_8c41d2"}' result = client.call("medium", "get_story", {"story_id":"story_8c41d2"}) const result = await cotlet.call('medium', 'get_story', {"story_id":"story_8c41d2"}); cotlet call medium get_story --data '{"story_id":"story_8c41d2"}' Example response
{
"story_id": "3f9a1c27b84e",
"title": "Shipping small",
"subtitle": "Why small releases win",
"body_markdown": "Small releases beat big ones.\n\nHere is why.",
"tags": [
"Python",
"Programming"
],
"canonical_url": "https://yourname.dev/blog/shipping-small",
"paywalled": false,
"status": "published"
} Get story stats
readPOST https://cotlet.dev/api/v1/medium/get_story_stats
Returns the numbers from the story's detailed stats page. Member read ratio is member reads divided by member views, the figure Medium uses to adjust earnings. Earnings appear only for stories behind the paywall.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/get_story_stats \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"story_8c41d2"}' result = client.call("medium", "get_story_stats", {"story_id":"story_8c41d2"}) const result = await cotlet.call('medium', 'get_story_stats', {"story_id":"story_8c41d2"}); cotlet call medium get_story_stats --data '{"story_id":"story_8c41d2"}' Example response
{
"story_id": "3f9a1c27b84e",
"views": 1840,
"reads": 912,
"member_read_ratio": 0.42,
"claps": 143,
"responses": 6,
"earnings": {
"amount": 18.4,
"currency": "USD"
}
} Get account stats
readPOST https://cotlet.dev/api/v1/medium/get_account_stats
Returns the account-level figures Medium shows on your stats page for one month. Use list_stories and get_story_stats to find which stories drove them.
Parameters
| Name | Type | Description |
|---|---|---|
| month | string | YYYY-MM. Default: current month. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/get_account_stats \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"month":"2026-09"}' result = client.call("medium", "get_account_stats", {"month":"2026-09"}) const result = await cotlet.call('medium', 'get_account_stats', {"month":"2026-09"}); cotlet call medium get_account_stats --data '{"month":"2026-09"}' Example response
{
"month": "2026-09",
"views": 9420,
"reads": 4310
} List responses
readPOST https://cotlet.dev/api/v1/medium/list_responses
Lists the responses under one story, with the author, text and whether you already replied. Hidden responses are not returned. Pass response_id to reply_to_response.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
| unanswered_only | boolean | Skip responses you already replied to. |
| cursor | string | Next page cursor from the previous call. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/list_responses \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"story_8c41d2"}' result = client.call("medium", "list_responses", {"story_id":"story_8c41d2"}) const result = await cotlet.call('medium', 'list_responses', {"story_id":"story_8c41d2"}); cotlet call medium list_responses --data '{"story_id":"story_8c41d2"}' Example response
{
"responses": [
{
"response_id": "r_41b9c2",
"author": "Dana Whitfield",
"body": "The third example has a typo.",
"created_at": "2026-10-03T11:20:00Z",
"replied": false
}
],
"next_cursor": null
} List publications
readPOST https://cotlet.dev/api/v1/medium/list_publications
Returns every publication you belong to with your role. Use the publication_id with submit_to_publication. Each publication sets its own rules for what it accepts.
Parameters
No parameters.
Request
curl -X POST https://cotlet.dev/api/v1/medium/list_publications \
-H "Authorization: Bearer $COTLET_API_KEY" result = client.call("medium", "list_publications") const result = await cotlet.call('medium', 'list_publications'); cotlet call medium list_publications Example response
{
"publications": [
{
"publication_id": "pub_7d21",
"name": "Better Programming",
"role": "writer"
}
]
} Create draft
writePOST https://cotlet.dev/api/v1/medium/create_draft
Creates a draft from Markdown and returns its story_id. The draft is private. It reaches no one and is deleted or edited in one call, so it asks for no confirmation.
Parameters
| Name | Type | Description |
|---|---|---|
| title required | string | Story title. |
| body_markdown required | string | Story body. Images by URL. |
| subtitle | string | Shown under the title and in previews. |
| tags | string[] | Up to 5 tags. |
| canonical_url | string | Original URL if the story was first published elsewhere. |
| paywalled | boolean | Member-only story (Partner Program). Default: false. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/create_draft \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Shipping small","subtitle":"Why small releases win","body_markdown":"Small releases beat big ones.\n\n## Here is why","tags":["Python","Programming","Software Engineering"],"paywalled":true}' result = client.call("medium", "create_draft", {"title":"Shipping small","subtitle":"Why small releases win","body_markdown":"Small releases beat big ones.\n\n## Here is why","tags":["Python","Programming","Software Engineering"],"paywalled":True}) const result = await cotlet.call('medium', 'create_draft', {"title":"Shipping small","subtitle":"Why small releases win","body_markdown":"Small releases beat big ones.\n\n## Here is why","tags":["Python","Programming","Software Engineering"],"paywalled":true}); cotlet call medium create_draft --data '{"title":"Shipping small","subtitle":"Why small releases win","body_markdown":"Small releases beat big ones.\n\n## Here is why","tags":["Python","Programming","Software Engineering"],"paywalled":true}' Example response
{
"story_id": "3f9a1c27b84e",
"status": "draft",
"url": "https://medium.com/p/3f9a1c27b84e/edit"
} Import story
writePOST https://cotlet.dev/api/v1/medium/import_story
Runs Medium's importer on a public URL. The draft keeps the original date and gets a canonical link to the source. If Medium cannot read the page, the call fails and create_draft is the fallback. The draft is private, so no confirmation.
Parameters
| Name | Type | Description |
|---|---|---|
| url required | string | Public URL of the original post. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/import_story \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://yourname.dev/blog/shipping-small"}' result = client.call("medium", "import_story", {"url":"https://yourname.dev/blog/shipping-small"}) const result = await cotlet.call('medium', 'import_story', {"url":"https://yourname.dev/blog/shipping-small"}); cotlet call medium import_story --data '{"url":"https://yourname.dev/blog/shipping-small"}' Example response
{
"story_id": "3f9a1c27b84e",
"status": "draft",
"canonical_url": "https://yourname.dev/blog/shipping-small"
} POST https://cotlet.dev/api/v1/medium/update_story
Changes only the fields you send. On a published story the change is visible to readers at once. By default you confirm each call; you can switch that off for this tool.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
| fields required | object | Any of title, subtitle, body_markdown, tags, canonical_url, paywalled. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/update_story \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"3f9a1c27b84e","fields":{"body_markdown":"Small releases beat big ones.\n\nMore on [yourname.dev](https://yourname.dev)."}}' result = client.call("medium", "update_story", {"story_id":"3f9a1c27b84e","fields":{"body_markdown":"Small releases beat big ones.\n\nMore on [yourname.dev](https://yourname.dev)."}}) const result = await cotlet.call('medium', 'update_story', {"story_id":"3f9a1c27b84e","fields":{"body_markdown":"Small releases beat big ones.\n\nMore on [yourname.dev](https://yourname.dev)."}}); cotlet call medium update_story --data '{"story_id":"3f9a1c27b84e","fields":{"body_markdown":"Small releases beat big ones.\n\nMore on [yourname.dev](https://yourname.dev)."}}' Example response
{
"story_id": "3f9a1c27b84e",
"status": "published",
"updated_fields": [
"tags"
]
} POST https://cotlet.dev/api/v1/medium/publish_story
Publishes a draft under your name, or in the publication it belongs to. Readers and followers can see it at once. By default you confirm each call; you can switch that off for this tool.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/publish_story \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"story_8c41d2"}' result = client.call("medium", "publish_story", {"story_id":"story_8c41d2"}) const result = await cotlet.call('medium', 'publish_story', {"story_id":"story_8c41d2"}); cotlet call medium publish_story --data '{"story_id":"story_8c41d2"}' Example response
{
"story_id": "3f9a1c27b84e",
"status": "published",
"url": "https://medium.com/@yourname/shipping-small-3f9a1c27b84e"
} POST https://cotlet.dev/api/v1/medium/schedule_story
Schedules a draft. Medium publishes it at the set time, and the story stays in your drafts until then. By default you confirm each call; you can switch that off for this tool.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
| publish_at required | datetime | ISO 8601 with time zone. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/schedule_story \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"}' result = client.call("medium", "schedule_story", {"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"}) const result = await cotlet.call('medium', 'schedule_story', {"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"}); cotlet call medium schedule_story --data '{"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"}' Example response
{
"story_id": "3f9a1c27b84e",
"status": "scheduled",
"publish_at": "2026-10-15T09:00:00-04:00"
} POST https://cotlet.dev/api/v1/medium/submit_to_publication
Submits a draft to a publication. Its editors review it and decide when to publish. A publication can accept only drafts, only published stories, or both. By default you confirm each call; you can switch that off for this tool.
Parameters
| Name | Type | Description |
|---|---|---|
| story_id required | string | Story ID from list_stories. |
| publication_id required | string | Publication ID from list_publications. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/submit_to_publication \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"story_id":"story_8c41d2","publication_id":"publication_8c41d2"}' result = client.call("medium", "submit_to_publication", {"story_id":"story_8c41d2","publication_id":"publication_8c41d2"}) const result = await cotlet.call('medium', 'submit_to_publication', {"story_id":"story_8c41d2","publication_id":"publication_8c41d2"}); cotlet call medium submit_to_publication --data '{"story_id":"story_8c41d2","publication_id":"publication_8c41d2"}' Example response
{
"story_id": "3f9a1c27b84e",
"publication_id": "pub_7d21",
"status": "submitted"
} POST https://cotlet.dev/api/v1/medium/reply_to_response
Posts a reply under one response, under your name. By default you confirm each call; you can switch that off for this tool.
Parameters
| Name | Type | Description |
|---|---|---|
| response_id required | string | Response ID from list_responses. |
| body required | string | Reply text. |
Request
curl -X POST https://cotlet.dev/api/v1/medium/reply_to_response \
-H "Authorization: Bearer $COTLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"response_id":"response_8c41d2","body":"body"}' result = client.call("medium", "reply_to_response", {"response_id":"response_8c41d2","body":"body"}) const result = await cotlet.call('medium', 'reply_to_response', {"response_id":"response_8c41d2","body":"body"}); cotlet call medium reply_to_response --data '{"response_id":"response_8c41d2","body":"body"}' Example response
{
"response_id": "r_41b9c2",
"reply_id": "r_41ba07",
"status": "published"
}