> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.beehiiv.com/api-reference/posts/aggregate-stats/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.beehiiv.com/_mcp/server.
# Get aggregate stats OAuth Scope: posts:read
GET https://api.beehiiv.com/v2/publications/{publicationId}/posts/aggregate_stats
Retrieve aggregate stats for all posts.The `clicks` breakdown returns the top 1,000 URLs by total clicks across the matched posts, and URLs that differ only by a per-subscriber UUID (polls, preference pages, etc.) are grouped into a single row\.Stats for large publications are served from a cache that is refreshed in the background about once an hour; `computed_at` is when the returned numbers were calculated.
Reference: https://developers.beehiiv.com/api-reference/posts/aggregate-stats
## Authentication
- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer `, where token is your auth token.
## Request
### Path parameters
- `publicationId` (string, required) — The prefixed ID of the publication object
### Query parameters
- `audience` (enum, optional, default: all) — Optionally filter the results by audience
- Allowed values: `free`, `premium`, `all`
- `platform` (enum, optional, default: all) — Optionally filter the results by platform.`web` - Posts only published to web.`email` - Posts only published to email.`both` - Posts published to email and web.`all` - Does not restrict results by platform.
- Allowed values: `web`, `email`, `both`, `all`
- `status` (enum, optional, default: all) — Optionally filter the results by the status of the post.`draft` - not been scheduled.`confirmed` - The post will be active after the `scheduled_at`.`archived` - The post is no longer active.`all` - Does not restrict results by status.
- Allowed values: `draft`, `confirmed`, `archived`, `all`
- `content_tags[]` (list of string, optional) — Optionally filter posts by content\_tags. Adding a content tag will return any post with that content tag associated to it.Example: Filtering for `content_tags: ["sales","closing"]` will return results of posts that have *either* sales or closing content\_tags.
- `authors[]` (list of string, optional) — Optionally filter posts by their authors. Adding an author name will return any post with that author associated to it (case-insensitive).Example: Filtering for `authors: ["John Doe","Jane Smith"]` will return results of posts that have *either* John Doe or Jane Smith as authors.
- `hidden_from_feed` (enum, optional, default: all) — Optionally filter the results by the `hidden_from_feed` attribute of the post.`all` - Does not restrict results by `hidden_from_feed`.`true` - Only return posts hidden from the feed.`false` - Only return posts that are visible on the feed.
- Allowed values: `all`, `true`, `false`
## Response
### 200
OK
- `data` (PostsAggregateStatsResponseStats, required)
## Errors
### 400 Bad Request Error
Bad Request
- `status` (integer, required)
- `statusText` (string, required)
- `errors` (list of ErrorDetail, required)
### 401 Unauthorized Error
Unauthorized. The API key or OAuth access token is missing, invalid, or expired.
- `status` (integer, required)
- `statusText` (string, required)
- `errors` (list of ErrorDetail, required)
### 404 Not Found Error
Resource Not Found
- `status` (integer, required)
- `statusText` (string, required)
- `errors` (list of ErrorDetail, required)
### 429 Too Many Requests Error
Rate Limit Exceeded
- `status` (integer, required)
- `statusText` (string, required)
- `errors` (list of ErrorDetail, required)
### 500 Internal Server Error
Internal Server Error
- `status` (integer, required)
- `statusText` (string, required)
- `errors` (list of ErrorDetail, required)
## Types
### PostsAggregateStatsResponseStats
- `stats` (PostStats, required) — Optional list of stats for a post. Retrievable by including `expand: [stats]` in the post request body. **Note:** If a timeout occurs while aggregating stats, subsequent requests may return consolidated click metrics rather than individual raw click metrics.
- `computed_at` (integer, required) — When the returned stats were calculated. Measured in seconds since the Unix epoch.
### ErrorDetail
- `message` (string, required)
- `code` (string, required)
### PostStats
Optional list of stats for a post. Retrievable by including `expand: [stats]` in the post request body. **Note:** If a timeout occurs while aggregating stats, subsequent requests may return consolidated click metrics rather than individual raw click metrics.
- `email` (PostStatsEmail, optional) — Stats scoped only to email recipients. Not relevant for posts published only to web
- `web` (PostStatsWeb, optional) — Stats scoped only to web views. Not relevant for posts published only to email
- `clicks` (list of ClickStats, optional) — An array of click statistics for each URL in the post
- `upgrades` (integer, optional, default: 0) — Total number of free-to-premium upgrades attributed to this post. Only populated when the publication has a paid subscription tier.
### PostStatsEmail
Stats scoped only to email recipients. Not relevant for posts published only to web
- `recipients` (integer, optional, default: 0) — Total number of email recipients
- `delivered` (integer, optional, default: 0) — Total number of emails delivered
- `opens` (integer, optional, default: 0) — Total number of email opens
- `unique_opens` (integer, optional, default: 0) — Total number of unique email opens
- `open_rate` (double, optional) — The percentage of emails that have been opened
- `clicks` (integer, optional, default: 0) — Total number of email clicks
- `unique_clicks` (integer, optional, default: 0) — Unique number of email clicks
- `verified_clicks` (integer, optional, default: 0) — Total number of verified human email clicks across all URLs in this post. Verified clicks have passed bot detection and are confirmed to be from real subscribers.
- `unique_verified_clicks` (integer, optional, default: 0) — Unique number of verified human email clicks across all URLs in this post. Only counts the first verified click per subscriber.
- `click_rate` (double, optional) — The percentage of emails that have been clicked
- `unsubscribes` (integer, optional, default: 0) — Total number of email unsubscribes
- `spam_reports` (integer, optional, default: 0) — The number of subscribers that reported this post email as spam
### PostStatsWeb
Stats scoped only to web views. Not relevant for posts published only to email
- `views` (integer, optional, default: 0) — Total number of web views
- `clicks` (integer, optional, default: 0) — Total number of web clicks
### ClickStats
Details about specific URL's click stats from a post.
- `url` (string, optional) — The URL the stats are for
- `base_url` (string, optional) — The canonical URL with all query parameters and fragments removed. Derived by stripping everything after the '?' and '#' characters. Preserves the protocol, host (including casing), port, and path exactly as they appear in the original URL. Guaranteed to be present whenever url is present, enabling grouping of clicks by destination regardless of tracking parameters.
- `email` (PostClickStatsEmail, optional) — URL stats scoped only to email recipients. Not relevant for posts published only to web
- `web` (PostClickStatsWeb, optional) — Stats scoped only to web views. Not relevant for posts published only to email
- `total_clicks` (integer, optional)
- `total_unique_clicks` (integer, optional)
- `total_click_through_rate` (double, optional) — The percentage of clicks on the URL compared to the total number of recipients and web views
### PostClickStatsEmail
URL stats scoped only to email recipients. Not relevant for posts published only to web
- `clicks` (integer, optional)
- `unique_clicks` (integer, optional)
- `verified_clicks` (integer, optional) — Total number of verified human email clicks on this URL. Verified clicks have passed bot detection and are confirmed to be from real subscribers.
- `unique_verified_clicks` (integer, optional) — Unique number of verified human email clicks on this URL. Only counts the first verified click per subscriber.
- `click_through_rate` (double, optional) — The percentage of email clicks on the URL compared to the total number of recipients
### PostClickStatsWeb
Stats scoped only to web views. Not relevant for posts published only to email
- `clicks` (integer, optional)
- `unique_clicks` (integer, optional)
- `click_through_rate` (double, optional) — The percentage of clicks on the URL compared to the total number of web views
## Examples
**Response**
```json
{
"data": {
"stats": {
"email": {
"recipients": 100,
"delivered": 100,
"opens": 50,
"unique_opens": 45,
"open_rate": 45,
"clicks": 10,
"unique_clicks": 8,
"verified_clicks": 9,
"unique_verified_clicks": 7,
"click_rate": 8,
"unsubscribes": 1,
"spam_reports": 1
},
"web": {
"views": 200,
"clicks": 40
},
"clicks": [
{
"url": "https://www.google.com",
"base_url": "https://www.google.com",
"email": {
"clicks": 10,
"unique_clicks": 8,
"verified_clicks": 9,
"unique_verified_clicks": 7,
"click_through_rate": 80
},
"web": {
"clicks": 40,
"unique_clicks": 40,
"click_through_rate": 20
},
"total_clicks": 50,
"total_unique_clicks": 48,
"total_click_through_rate": 40
}
],
"upgrades": 5
},
"computed_at": 1700000000
}
}
```
**SDK Code**
```python
import requests
url = "https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats"
headers = {"Authorization": "Bearer "}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript
const url = 'https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats';
const options = {method: 'GET', headers: {Authorization: 'Bearer '}};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
```
```go
package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer ")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
```
```ruby
require 'uri'
require 'net/http'
url = URI("https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer '
response = http.request(request)
puts response.read_body
```
```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
HttpResponse response = Unirest.get("https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats")
.header("Authorization", "Bearer ")
.asString();
```
```php
request('GET', 'https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats', [
'headers' => [
'Authorization' => 'Bearer ',
],
]);
echo $response->getBody();
```
```csharp
using RestSharp;
var client = new RestClient("https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer ");
IRestResponse response = client.Execute(request);
```
```swift
import Foundation
let headers = ["Authorization": "Bearer "]
let request = NSMutableURLRequest(url: NSURL(string: "https://api.beehiiv.com/v2/publications/pub_00000000-0000-0000-0000-000000000000/posts/aggregate_stats")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
```
> Build your audience with beehiiv's API and SDKs. Create new subscribers and posts, get real-time notifications of subscription activity, and more.