# Medium API

> Every Medium endpoint on Cotlet: inputs, JSON Schema, cURL, Python and Node.js examples and an example response for each call.

Source: https://cotlet.dev/docs/api/medium

Docs sections: [Get started](https://cotlet.dev/docs.md) · [REST API](https://cotlet.dev/docs/api.md) · [MCP](https://cotlet.dev/docs/mcp.md) · [SDKs & CLI](https://cotlet.dev/docs/sdks.md) · [Integrations](https://cotlet.dev/docs/integrations.md)

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: https://cotlet.dev/integrations/medium-api-mcp-server

## Endpoints

| 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

`POST https://cotlet.dev/api/v1/medium/list_stories`

Access: read.

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 | Required | Description |
|---|---|---|---|
| `status` | enum | no | published, draft or scheduled. Default: published. |
| `limit` | integer | no | 1 to 50. Default: 20. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/medium/list_stories \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("medium", "list_stories")
```

**Node.js**

```js
const result = await cotlet.call('medium', 'list_stories');
```

**CLI**

```bash
cotlet call medium list_stories
```

### Example response

```json
{
  "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

`POST https://cotlet.dev/api/v1/medium/get_story`

Access: read.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "get_story", {"story_id":"story_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'get_story', {"story_id":"story_8c41d2"});
```

**CLI**

```bash
cotlet call medium get_story --data '{"story_id":"story_8c41d2"}'
```

### Example response

```json
{
  "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

`POST https://cotlet.dev/api/v1/medium/get_story_stats`

Access: read.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "get_story_stats", {"story_id":"story_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'get_story_stats', {"story_id":"story_8c41d2"});
```

**CLI**

```bash
cotlet call medium get_story_stats --data '{"story_id":"story_8c41d2"}'
```

### Example response

```json
{
  "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

`POST https://cotlet.dev/api/v1/medium/get_account_stats`

Access: read.

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 | Required | Description |
|---|---|---|---|
| `month` | string | no | YYYY-MM. Default: current month. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "get_account_stats", {"month":"2026-09"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'get_account_stats', {"month":"2026-09"});
```

**CLI**

```bash
cotlet call medium get_account_stats --data '{"month":"2026-09"}'
```

### Example response

```json
{
  "month": "2026-09",
  "views": 9420,
  "reads": 4310
}
```

## List responses

`POST https://cotlet.dev/api/v1/medium/list_responses`

Access: read.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |
| `unanswered_only` | boolean | no | Skip responses you already replied to. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "list_responses", {"story_id":"story_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'list_responses', {"story_id":"story_8c41d2"});
```

**CLI**

```bash
cotlet call medium list_responses --data '{"story_id":"story_8c41d2"}'
```

### Example response

```json
{
  "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

`POST https://cotlet.dev/api/v1/medium/list_publications`

Access: read.

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**

```bash
curl -X POST https://cotlet.dev/api/v1/medium/list_publications \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("medium", "list_publications")
```

**Node.js**

```js
const result = await cotlet.call('medium', 'list_publications');
```

**CLI**

```bash
cotlet call medium list_publications
```

### Example response

```json
{
  "publications": [
    {
      "publication_id": "pub_7d21",
      "name": "Better Programming",
      "role": "writer"
    }
  ]
}
```

## Create draft

`POST https://cotlet.dev/api/v1/medium/create_draft`

Access: write.

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 | Required | Description |
|---|---|---|---|
| `title` | string | yes | Story title. |
| `body_markdown` | string | yes | Story body. Images by URL. |
| `subtitle` | string | no | Shown under the title and in previews. |
| `tags` | string[] | no | Up to 5 tags. |
| `canonical_url` | string | no | Original URL if the story was first published elsewhere. |
| `paywalled` | boolean | no | Member-only story (Partner Program). Default: false. |

### Request

**cURL**

```bash
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}'
```

**Python**

```python
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})
```

**Node.js**

```js
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});
```

**CLI**

```bash
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

```json
{
  "story_id": "3f9a1c27b84e",
  "status": "draft",
  "url": "https://medium.com/p/3f9a1c27b84e/edit"
}
```

## Import story

`POST https://cotlet.dev/api/v1/medium/import_story`

Access: write.

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 | Required | Description |
|---|---|---|---|
| `url` | string | yes | Public URL of the original post. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "import_story", {"url":"https://yourname.dev/blog/shipping-small"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'import_story', {"url":"https://yourname.dev/blog/shipping-small"});
```

**CLI**

```bash
cotlet call medium import_story --data '{"url":"https://yourname.dev/blog/shipping-small"}'
```

### Example response

```json
{
  "story_id": "3f9a1c27b84e",
  "status": "draft",
  "canonical_url": "https://yourname.dev/blog/shipping-small"
}
```

## Update story

`POST https://cotlet.dev/api/v1/medium/update_story`

Access: write. Confirm: waits for the user's OK before it runs.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |
| `fields` | object | yes | Any of title, subtitle, body_markdown, tags, canonical_url, paywalled. |

### Request

**cURL**

```bash
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)."}}'
```

**Python**

```python
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)."}})
```

**Node.js**

```js
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)."}});
```

**CLI**

```bash
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

```json
{
  "story_id": "3f9a1c27b84e",
  "status": "published",
  "updated_fields": [
    "tags"
  ]
}
```

## Publish story

`POST https://cotlet.dev/api/v1/medium/publish_story`

Access: write. Confirm: waits for the user's OK before it runs.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "publish_story", {"story_id":"story_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'publish_story', {"story_id":"story_8c41d2"});
```

**CLI**

```bash
cotlet call medium publish_story --data '{"story_id":"story_8c41d2"}'
```

### Example response

```json
{
  "story_id": "3f9a1c27b84e",
  "status": "published",
  "url": "https://medium.com/@yourname/shipping-small-3f9a1c27b84e"
}
```

## Schedule story

`POST https://cotlet.dev/api/v1/medium/schedule_story`

Access: write. Confirm: waits for the user's OK before it runs.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |
| `publish_at` | datetime | yes | ISO 8601 with time zone. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "schedule_story", {"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'schedule_story', {"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"});
```

**CLI**

```bash
cotlet call medium schedule_story --data '{"story_id":"3f9a1c27b84e","publish_at":"2026-10-15T09:00:00-04:00"}'
```

### Example response

```json
{
  "story_id": "3f9a1c27b84e",
  "status": "scheduled",
  "publish_at": "2026-10-15T09:00:00-04:00"
}
```

## Submit to publication

`POST https://cotlet.dev/api/v1/medium/submit_to_publication`

Access: write. Confirm: waits for the user's OK before it runs.

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 | Required | Description |
|---|---|---|---|
| `story_id` | string | yes | Story ID from list_stories. |
| `publication_id` | string | yes | Publication ID from list_publications. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "submit_to_publication", {"story_id":"story_8c41d2","publication_id":"publication_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'submit_to_publication', {"story_id":"story_8c41d2","publication_id":"publication_8c41d2"});
```

**CLI**

```bash
cotlet call medium submit_to_publication --data '{"story_id":"story_8c41d2","publication_id":"publication_8c41d2"}'
```

### Example response

```json
{
  "story_id": "3f9a1c27b84e",
  "publication_id": "pub_7d21",
  "status": "submitted"
}
```

## Reply to response

`POST https://cotlet.dev/api/v1/medium/reply_to_response`

Access: write. Confirm: waits for the user's OK before it runs.

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 | Required | Description |
|---|---|---|---|
| `response_id` | string | yes | Response ID from list_responses. |
| `body` | string | yes | Reply text. |

### Request

**cURL**

```bash
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"}'
```

**Python**

```python
result = client.call("medium", "reply_to_response", {"response_id":"response_8c41d2","body":"body"})
```

**Node.js**

```js
const result = await cotlet.call('medium', 'reply_to_response', {"response_id":"response_8c41d2","body":"body"});
```

**CLI**

```bash
cotlet call medium reply_to_response --data '{"response_id":"response_8c41d2","body":"body"}'
```

### Example response

```json
{
  "response_id": "r_41b9c2",
  "reply_id": "r_41ba07",
  "status": "published"
}
```
