curl --request GET \
--url https://api.scripe.io/v1/analytics/posts \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.scripe.io/v1/analytics/posts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.scripe.io/v1/analytics/posts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scripe.io/v1/analytics/posts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.scripe.io/v1/analytics/posts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.scripe.io/v1/analytics/posts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scripe.io/v1/analytics/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "<string>",
"post_id": "post_a1b2c3d4e5f6g7h8",
"project_id": "<string>",
"linkedin_post_id": "urn:li:activity:7302247578123788289",
"permalink": "<string>",
"content": "<string>",
"content_truncated": true,
"content_type": "<string>",
"media_type": "<string>",
"posted_at": "2023-11-07T05:31:56Z",
"metrics": {
"views": 123,
"likes": 123,
"comments": 123,
"shares": 123,
"total_engagement": 123,
"engagement_rate": 123
}
}
],
"pagination": {
"total": 123,
"limit": 123,
"offset": 123,
"has_more": true
},
"meta": {
"requested_post_ids": [
"<string>"
],
"unmeasured": [
{
"post_id": "<string>",
"reason": "not_published",
"detail": "<string>"
}
]
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}Per-post analytics (library)
Offset-paginated per-post LinkedIn metrics (views, likes, comments, shares, engagement rate) for one or more projects, newest first.
If projectId is omitted, fetches posts across all projects
available to the caller in the workspace.
Rows describe posts that are live on LinkedIn, which is not
the same object as the Scripe draft they may have come from — see
the three ids on PostAnalytics.
content is an excerpt by default (content=preview). A full
page of 50 posts carries ~66 000 characters of post bodies, which
is the wrong default for an endpoint whose job is ranking. Pass
content=full when you need the whole body.
Required scope: analytics:read.
curl --request GET \
--url https://api.scripe.io/v1/analytics/posts \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.scripe.io/v1/analytics/posts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.scripe.io/v1/analytics/posts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scripe.io/v1/analytics/posts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.scripe.io/v1/analytics/posts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.scripe.io/v1/analytics/posts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scripe.io/v1/analytics/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "<string>",
"post_id": "post_a1b2c3d4e5f6g7h8",
"project_id": "<string>",
"linkedin_post_id": "urn:li:activity:7302247578123788289",
"permalink": "<string>",
"content": "<string>",
"content_truncated": true,
"content_type": "<string>",
"media_type": "<string>",
"posted_at": "2023-11-07T05:31:56Z",
"metrics": {
"views": 123,
"likes": 123,
"comments": 123,
"shares": 123,
"total_engagement": 123,
"engagement_rate": 123
}
}
],
"pagination": {
"total": 123,
"limit": 123,
"offset": 123,
"has_more": true
},
"meta": {
"requested_post_ids": [
"<string>"
],
"unmeasured": [
{
"post_id": "<string>",
"reason": "not_published",
"detail": "<string>"
}
]
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}Authorizations
Pass Authorization: Bearer scripe_sk_live_<...> (or
scripe_sk_test_<...> for test keys) on every request. Keys
are scoped to a single workspace and can be revoked from the
Scripe dashboard.
The same header also accepts an OAuth 2.1 access token
(scripe_oat_*); both credentials share one scope vocabulary
and every operation below documents the scope it requires.
An API key can hold every scope named on this surface except
webhooks:manage, which is grantable to OAuth tokens only
today — the webhook-endpoint operations answer
403 scope_missing to every API key. Operations that name no
scope accept any valid token of the workspace.
Headers
Pin the API version. Format YYYY-MM-DD. Omit to receive the
currently rolling default. Unknown versions return 400 version_unsupported.
"2026-08-10"
Query Parameters
One project id, or a comma-separated list. If omitted, defaults to all available projects.
"proj_a1b2c3d4e5f6g7h8"
Report on specific Scripe posts: one post_… id or a
comma-separated list of up to 50. This is how "how did THIS
post do?" is answered — the response contains only those
posts, with no date range to work out and no page to scan.
A named post that has no metrics is reported in meta
rather than silently absent, because an empty page reads as
"no engagement" when the truth may be "never published".
An id the reported-on project(s) do not contain is a 400,
never an empty page.
"post_a1b2c3d4e5f6g7h8"
ISO 8601 or YYYY-MM-DD lower bound (inclusive).
ISO 8601 or YYYY-MM-DD upper bound (inclusive).
Page size. Default 50, max 200. Values above the max are clamped silently; only a non-integer or a value below 1 is rejected with bad_pagination.
1 <= x <= 200x >= 0Ordering. impressions ranks by views and engagement by total interactions — neither ranks by engagement rate.
recent, impressions, engagement How much of each post body to return. preview returns the first ~280 characters cut on a word boundary and sets content_truncated; full returns the whole body; none omits it (content: null, content_truncated: true when a body exists).
preview, full, none Response
Page of per-post analytics.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Present only when postId was passed. It exists so a filtered
answer can be read correctly: data: [] on its own says "no
engagement", which is a claim about a post that may never have
been published at all.
Show child attributes
Show child attributes