Substack API endpoint
Returns one page of a category's public publication leaderboard. Each row carries the publication's subscriber signals and its full subscription offering (web plans with multi-currency pricing, App Store plans, and per-tier benefits). Subscriber totals are published to roughly three significant figures and are opt-in per writer; paid subscriber figures are only ever buckets. Upstream serves 25 publications per page and accepts pages 0-26, so at most 675 publications are reachable per category and type.
/substack/categorycategory_id: required category id from /api/v1/substack/categories
whose in_category_listing is true. Numeric, or the string id
podcast. The bestseller id is rejected here — use
/api/v1/substack/leaderboard for it.type: optional ranking to read. Allowed values: paid, free.
Defaults to paid.page: optional zero-based page. Allowed range 0-26.Returns one page of a category's public publication leaderboard. Every row carries both the publication's subscriber signals and its full subscription offering, so this endpoint alone answers "who publishes here, how big are they, and what do they sell".
For the trending ("Rising") ranking and for author data alongside each
publication, use /api/v1/substack/leaderboard
instead; this endpoint cannot reach either.
Substack serves exactly 25 publications per page and accepts pages 0-26,
so at most 675 publications are reachable per category per type. A
page above 26 is rejected with an invalid-parameter error rather than
being passed upstream. Requesting more than 25 rows is not possible.
total / total_display: total subscribers, free and paid combined,
published to roughly three significant figures. Opt-in per writer.total_hidden: true when the writer has hidden the count. total is
then 0, which does not mean zero subscribers.total_rough: prose bucket for the total, e.g.
Hundreds of thousands of subscribers.paid_rough / paid_tier: the only paid-subscriber signals, both
buckets. Substack publishes no exact paid count on any public surface.| Field | Filled in, of 25 checked | Values in the sample |
|---|---|---|
| id | 25 | |
| name | 25 | |
| subdomain | 25 | |
| custom_domain | 18 | |
| url | 25 | newsletter.semianalysis.com and 24 other hosts |
| hero_text | 25 | median 121 characters |
| logo_url | 23 | substackcdn.com |
| author_id | 25 | |
| author_name | 25 | |
| author_handle | 25 | |
| language | 25 | |
| explicit | 25 | true in 0 of 25 |
| payments_enabled | 25 | true in 25 of 25 |
| subscribers | 25 | |
| subscription | 25 |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| category_id | string | Yes | Category id from /substack/categories. Numeric for 32 categories, or the string podcast. | 4 | |
| type | string | No | Ranking to read Allowed values: paid, free. | ||
| page | integer | No | Zero-based page, 0-26 | ||
| x-api-key (header) | string | Yes | API key required |
curl -X GET "https://api.crawlora.net/api/v1/substack/category?category_id=4&type=paid&page=1" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"data": {
"category_id": "4",
"type": "paid",
"page": 0,
"has_more": true,
"publications": [
{
"id": 6349492,
"name": "SemiAnalysis",
"subdomain": "semianalysis",
"url": "https://semianalysis.substack.com",
"author_name": "Dylan Patel",
"payments_enabled": true,
"subscribers": {
"total": 315000,
"total_hidden": false,
"total_rough": "Hundreds of thousands of subscribers",
"paid_rough": "Thousands of paid subscribers",
"paid_tier": 1000
},
"subscription": {
"payments_state": "enabled",
"monthly_disabled": true,
"annual_disabled": false,
"group_discount_percent": 20,
"plans": [
{
"id": "yearly500usd_bc37ea9d",
"kind": "annual",
"nickname": "$500 a year",
"amount_cents": 50000,
"currency": "usd",
"interval": "year",
"interval_count": 1,
"active": true,
"currencies": [
{
"currency": "eur",
"amount_cents": 45000
}
]
}
],
"app_store_plans": [
{
"amount_cents": 6000,
"currency": "usd",
"period": "month",
"is_founding": false,
"display_price": "$60",
"active": true
}
],
"paid_benefits": [
"Full archive"
]
}
}
]
},
"msg": "success"
}Request schema
No body schema
Response schema
#/definitions/substack.categoryResponseDoc
| Field | Type | Required | Enum | Bounds | Example | Description |
|---|---|---|---|---|---|---|
| code | integer | No | 200 | |||
| data | substack.CategoryResult | No | ||||
| data.category_id | string | No | 4 | |||
| data.has_more | boolean | No | ||||
| data.page | integer | No | ||||
| data.publications | array | 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.type | string | No | paid | |||
| 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/category?category_id=4&type=paid&page=1" \
-H "x-api-key: $CRAWLORA_API_KEY"