Substack API endpoint
Returns one page of a category's bestseller leaderboard, pairing each ranked publication with its author. This is a different surface from /substack/category: it exposes the trending ranking (the site's "Rising" tab), validates its ranking values rather than silently defaulting, accepts the extra cross-category id `bestseller`, and returns the author alongside the publication. Use a category_id from /substack/categories whose in_leaderboard is true. Upstream serves 25 rows per page and accepts pages 0-25.
/substack/leaderboardcategory_id: required category id from /api/v1/substack/categories
whose in_leaderboard is true. Numeric, or the string ids podcast or
bestseller.type: optional ranking. Allowed values: trending, paid, all.
Defaults to trending.page: optional zero-based page. Allowed range 0-25./substack/categoryThey are separate upstream surfaces, not variants of one endpoint:
/substack/category | /substack/leaderboard | |
|---|---|---|
| Rankings | paid, free | trending, paid, all |
| Invalid ranking | Rejected by this API (upstream would silently default) | Rejected by upstream and this API |
| Author included | No | Yes, as entries[].author |
Accepts bestseller | No | Yes |
| Page range | 0-26 | 0-25 |
trending is the ranking behind the site's "Rising" tab and is not
reachable through /substack/category at all.
rank is derived from position within the page (page * 25 + index + 1);
the upstream sends no explicit rank field.
Two combinations are legitimately sparse: podcast + trending and
bestseller + paid/all return no rows. They are valid inputs, not
errors.
Subscriber and subscription fields on entries[].publication follow the
same rules as substack-category.
| Field | Filled in, of 25 checked | Values in the sample |
|---|---|---|
| rank | 25 | 1 to 25, median 13 |
| publication | 25 | |
| author | 23 |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| category_id | string | Yes | Category id from /substack/categories with in_leaderboard true. Numeric, or the string ids podcast or bestseller. | 96 | |
| type | string | No | Ranking to read Allowed values: trending, paid, all. | ||
| page | integer | No | Zero-based page, 0-25 | ||
| x-api-key (header) | string | Yes | API key required |
curl -X GET "https://api.crawlora.net/api/v1/substack/leaderboard?category_id=96&type=trending&page=1" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"data": {
"category_id": "96",
"type": "trending",
"page": 0,
"has_more": true,
"title": "Culture Bestsellers",
"entries": [
{
"rank": 1,
"publication": {
"id": 1521363,
"name": "Selvaggia Lucarelli",
"subdomain": "selvaggialucarelli",
"url": "https://selvaggialucarelli.substack.com",
"subscribers": {
"total": 231000,
"total_hidden": false,
"paid_rough": "Tens of thousands of paid subscribers"
}
},
"author": {
"id": 7851723,
"handle": "selvaggialucarelli",
"name": "Selvaggia Lucarelli",
"url": "https://substack.com/@selvaggialucarelli"
}
}
]
},
"msg": "success"
}Request schema
No body schema
Response schema
#/definitions/substack.leaderboardResponseDoc
| Field | Type | Required | Enum | Bounds | Example | Description |
|---|---|---|---|---|---|---|
| code | integer | No | 200 | |||
| data | substack.LeaderboardResult | No | ||||
| data.category_id | string | No | 96 | |||
| data.entries | array | No | ||||
| data.entries[].author | substack.User | No | User record, 32 fields: bestseller_tier, bio, handle, id, name, photo_url, profile_set_up_at, publications, slug, subscribers, subscriptions, url. Described under Get a public Substack writer profile API. | |||
| data.entries[].publication | substack.PublicationListItem | No | PublicationListItem record, 55 fields: author_handle, author_id, author_name, custom_domain, explicit, hero_text, id, language, logo_url, name, payments_enabled, subdomain and 3 more. Described under Substack Search API. | |||
| data.entries[].rank | integer | No | 1 | |||
| data.has_more | boolean | No | ||||
| data.page | integer | No | ||||
| data.title | string | No | Culture Bestsellers | |||
| data.type | string | No | trending | |||
| msg | string | No | success |
| Status | Description | Schema |
|---|---|---|
| 400 | Bad Request | #/definitions/app.Response |
| 429 | Too Many Requests | #/definitions/app.Response |
| 503 | Service Unavailable | #/definitions/app.Response |
Use environment variables for secrets and keep Crawlora API keys server-side.
curl -X GET "https://api.crawlora.net/api/v1/substack/leaderboard?category_id=96&type=trending&page=1" \
-H "x-api-key: $CRAWLORA_API_KEY"