PostPost

LinkedIn Analytics

Get engagement metrics (impressions, reactions, comments, shares) for your LinkedIn posts using the PostPost API.

Overview

PostPost provides analytics for LinkedIn posts through a single endpoint. You can retrieve individual metrics or all metrics at once.

API Reference

Endpoint:

POST https://api.postpost.dev/api/v1/linkedin-post-statistics

Headers:

Content-Type: application/json
x-api-key: YOUR_API_KEY

Request Body:

{
  "postedId": "urn:li:share:7434685316856377344",
  "platformId": "linkedin-ABC123",
  "queryTypes": ["ALL"]
}

Available Metrics

MetricDescription
IMPRESSIONNumber of times the post was displayed
MEMBERS_REACHEDUnique LinkedIn members who saw the post
REACTIONTotal reactions (likes, celebrates, etc.)
COMMENTNumber of comments on the post
RESHARENumber of times the post was shared

Request Parameters

ParameterTypeRequiredDescription
postedIdstringYesLinkedIn post URN (from get-post endpoint)
platformIdstringYesYour LinkedIn platform ID (e.g., linkedin-ABC123)
queryTypestringNoSingle metric: IMPRESSION, REACTION, etc.
queryTypesarrayNoMultiple metrics: ["IMPRESSION", "REACTION"] or ["ALL"]

Use either queryType (single) or queryTypes (multiple), not both.

Response Format

Single metric response:

{
  "success": true,
  "count": 4521,
  "cached": false
}

Multiple metrics response:

{
  "success": true,
  "metrics": {
    "IMPRESSION": 4521,
    "MEMBERS_REACHED": 3200,
    "REACTION": 89,
    "COMMENT": 15,
    "RESHARE": 12
  },
  "cached": false,
  "cachedMetrics": []
}

Complete JavaScript Implementation

const PUBLORA_API_KEY = process.env.PUBLORA_API_KEY;
const BASE_URL = 'https://api.postpost.dev/api/v1';

/**
 * Retrieve LinkedIn post analytics
 * @param {string} postedId - LinkedIn post URN (urn:li:share:xxx or urn:li:ugcPost:xxx)
 * @param {string} platformId - LinkedIn platform ID (linkedin-xxx)
 * @param {string|string[]} metrics - Single metric, array of metrics, or 'ALL'
 * @returns {Promise<Object>} Analytics data
 */
async function getLinkedInAnalytics(postedId, platformId, metrics = 'ALL') {
  // Validate inputs
  if (!postedId) {
    throw new Error('postedId is required');
  }
  if (!postedId.startsWith('urn:li:')) {
    throw new Error('postedId must be a LinkedIn URN (urn:li:share:xxx or urn:li:ugcPost:xxx)');
  }
  if (!platformId) {
    throw new Error('platformId is required');
  }

  // Build request body
  const body = {
    postedId,
    platformId
  };

  // Handle metrics parameter
  if (typeof metrics === 'string') {
    if (metrics === 'ALL') {
      body.queryTypes = ['ALL'];
    } else {
      body.queryType = metrics;
    }
  } else if (Array.isArray(metrics)) {
    body.queryTypes = metrics;
  }

  // Make API request
  const response = await fetch(`${BASE_URL}/linkedin-post-statistics`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': PUBLORA_API_KEY
    },
    body: JSON.stringify(body)
  });

  const data = await response.json();

  // Handle errors
  if (!response.ok) {
    const error = new Error(data.error || `HTTP ${response.status}`);
    error.status = response.status;
    error.response = data;

    switch (response.status) {
      case 400:
        error.message = `Invalid request: ${data.error}`;
        break;
      case 401:
        error.message = 'Invalid API key. Check your x-api-key header.';
        break;
      case 404:
        error.message = 'LinkedIn connection not found. Verify platformId.';
        break;
      case 500:
        error.message = 'Failed to fetch analytics. The post may not exist or analytics are not yet available.';
        break;
    }

    throw error;
  }

  return data;
}

// ============ USAGE EXAMPLES ============

// Example 1: Get all metrics
async function getAllMetrics(postedId, platformId) {
  try {
    const analytics = await getLinkedInAnalytics(postedId, platformId, 'ALL');
    console.log('Impressions:', analytics.metrics.IMPRESSION);
    console.log('Reactions:', analytics.metrics.REACTION);
    console.log('Comments:', analytics.metrics.COMMENT);
    console.log('Shares:', analytics.metrics.RESHARE);
    console.log('Reach:', analytics.metrics.MEMBERS_REACHED);
    return analytics;
  } catch (error) {
    console.error('Failed to get analytics:', error.message);
    throw error;
  }
}

// Example 2: Get specific metrics only
async function getEngagementMetrics(postedId, platformId) {
  try {
    const analytics = await getLinkedInAnalytics(
      postedId,
      platformId,
      ['REACTION', 'COMMENT', 'RESHARE']
    );
    return analytics.metrics;
  } catch (error) {
    console.error('Failed to get engagement:', error.message);
    throw error;
  }
}

// Example 3: Get single metric
async function getImpressions(postedId, platformId) {
  try {
    const analytics = await getLinkedInAnalytics(postedId, platformId, 'IMPRESSION');
    return analytics.count;
  } catch (error) {
    console.error('Failed to get impressions:', error.message);
    throw error;
  }
}

// Example 4: Get analytics for a post created via PostPost
async function getAnalyticsForPost(postGroupId, platformId) {
  // Step 1: Get the postedId from the post
  const postResponse = await fetch(`${BASE_URL}/get-post/${postGroupId}`, {
    headers: { 'x-api-key': PUBLORA_API_KEY }
  });
  const postData = await postResponse.json();

  const linkedInPost = postData.posts.find(p => p.platform === 'linkedin');
  if (!linkedInPost || !linkedInPost.postedId) {
    throw new Error('LinkedIn post not found or not yet published');
  }

  // Step 2: Get analytics
  return await getLinkedInAnalytics(linkedInPost.postedId, platformId, 'ALL');
}

// Run examples
const postedId = 'urn:li:share:7434685316856377344';
const platformId = 'linkedin-ABC123';

const allMetrics = await getAllMetrics(postedId, platformId);
const engagement = await getEngagementMetrics(postedId, platformId);
const impressions = await getImpressions(postedId, platformId);

Complete Python Implementation

import os
import requests
from typing import Union, List, Dict, Optional

PUBLORA_API_KEY = os.environ.get('PUBLORA_API_KEY')
BASE_URL = 'https://api.postpost.dev/api/v1'


class PostPostAnalyticsError(Exception):
    """Custom exception for analytics API errors."""
    def __init__(self, message: str, status_code: int = None, response: dict = None):
        super().__init__(message)
        self.status_code = status_code
        self.response = response


def get_linkedin_analytics(
    posted_id: str,
    platform_id: str,
    metrics: Union[str, List[str]] = 'ALL'
) -> Dict:
    """
    Retrieve LinkedIn post analytics.

    Args:
        posted_id: LinkedIn post URN (urn:li:share:xxx or urn:li:ugcPost:xxx)
        platform_id: LinkedIn platform ID (linkedin-xxx)
        metrics: Single metric string, list of metrics, or 'ALL'

    Returns:
        dict: Analytics data with metrics

    Raises:
        PostPostAnalyticsError: If the API request fails
        ValueError: If inputs are invalid
    """
    # Validate inputs
    if not posted_id:
        raise ValueError('posted_id is required')
    if not posted_id.startswith('urn:li:'):
        raise ValueError('posted_id must be a LinkedIn URN (urn:li:share:xxx or urn:li:ugcPost:xxx)')
    if not platform_id:
        raise ValueError('platform_id is required')

    # Build request body
    body = {
        'postedId': posted_id,
        'platformId': platform_id
    }

    # Handle metrics parameter
    if isinstance(metrics, str):
        if metrics == 'ALL':
            body['queryTypes'] = ['ALL']
        else:
            body['queryType'] = metrics
    elif isinstance(metrics, list):
        body['queryTypes'] = metrics

    # Make API request
    response = requests.post(
        f'{BASE_URL}/linkedin-post-statistics',
        headers={
            'Content-Type': 'application/json',
            'x-api-key': PUBLORA_API_KEY
        },
        json=body
    )

    data = response.json()

    # Handle errors
    if not response.ok:
        error_message = data.get('error', f'HTTP {response.status_code}')

        if response.status_code == 400:
            error_message = f'Invalid request: {error_message}'
        elif response.status_code == 401:
            error_message = 'Invalid API key. Check your x-api-key header.'
        elif response.status_code == 404:
            error_message = 'LinkedIn connection not found. Verify platform_id.'
        elif response.status_code == 500:
            error_message = 'Failed to fetch analytics. The post may not exist or analytics are not yet available.'

        raise PostPostAnalyticsError(error_message, response.status_code, data)

    return data


# ============ USAGE EXAMPLES ============

def get_all_metrics(posted_id: str, platform_id: str) -> Dict:
    """Get all available metrics for a post."""
    try:
        analytics = get_linkedin_analytics(posted_id, platform_id, 'ALL')
        print(f"Impressions: {analytics['metrics']['IMPRESSION']}")
        print(f"Reactions: {analytics['metrics']['REACTION']}")
        print(f"Comments: {analytics['metrics']['COMMENT']}")
        print(f"Shares: {analytics['metrics']['RESHARE']}")
        print(f"Reach: {analytics['metrics']['MEMBERS_REACHED']}")
        return analytics
    except (PostPostAnalyticsError, ValueError) as e:
        print(f"Failed to get analytics: {e}")
        raise


def get_engagement_metrics(posted_id: str, platform_id: str) -> Dict:
    """Get engagement metrics only (reactions, comments, shares)."""
    try:
        analytics = get_linkedin_analytics(
            posted_id,
            platform_id,
            ['REACTION', 'COMMENT', 'RESHARE']
        )
        return analytics['metrics']
    except (PostPostAnalyticsError, ValueError) as e:
        print(f"Failed to get engagement: {e}")
        raise


def get_impressions(posted_id: str, platform_id: str) -> int:
    """Get impression count for a post."""
    try:
        analytics = get_linkedin_analytics(posted_id, platform_id, 'IMPRESSION')
        return analytics['count']
    except (PostPostAnalyticsError, ValueError) as e:
        print(f"Failed to get impressions: {e}")
        raise


def get_analytics_for_post(post_group_id: str, platform_id: str) -> Dict:
    """Get analytics for a post created via PostPost."""
    # Step 1: Get the posted_id from the post
    post_response = requests.get(
        f'{BASE_URL}/get-post/{post_group_id}',
        headers={'x-api-key': PUBLORA_API_KEY}
    )
    post_data = post_response.json()

    linkedin_post = next(
        (p for p in post_data.get('posts', []) if p.get('platform') == 'linkedin'),
        None
    )

    if not linkedin_post or not linkedin_post.get('postedId'):
        raise ValueError('LinkedIn post not found or not yet published')

    # Step 2: Get analytics
    return get_linkedin_analytics(linkedin_post['postedId'], platform_id, 'ALL')


# Run examples
if __name__ == '__main__':
    posted_id = 'urn:li:share:7434685316856377344'
    platform_id = 'linkedin-ABC123'

    all_metrics = get_all_metrics(posted_id, platform_id)
    engagement = get_engagement_metrics(posted_id, platform_id)
    impressions = get_impressions(posted_id, platform_id)

cURL Examples

Get all metrics

curl -X POST "https://api.postpost.dev/api/v1/linkedin-post-statistics" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "postedId": "urn:li:share:7434685316856377344",
    "platformId": "linkedin-ABC123",
    "queryTypes": ["ALL"]
  }'

Get specific metrics

curl -X POST "https://api.postpost.dev/api/v1/linkedin-post-statistics" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "postedId": "urn:li:share:7434685316856377344",
    "platformId": "linkedin-ABC123",
    "queryTypes": ["IMPRESSION", "REACTION", "COMMENT"]
  }'

Get single metric

curl -X POST "https://api.postpost.dev/api/v1/linkedin-post-statistics" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "postedId": "urn:li:share:7434685316856377344",
    "platformId": "linkedin-ABC123",
    "queryType": "IMPRESSION"
  }'

Important Notes

Analytics Delay

LinkedIn analytics may take up to 24 hours to fully populate. Querying immediately after posting will return partial or zero data.

Caching

PostPost caches analytics for 30 minutes to improve performance and reduce LinkedIn API calls. The cached field in the response indicates if data was served from cache.

Finding the postedId

For posts created via PostPost, get the postedId from the get-post endpoint:

const response = await fetch(`https://api.postpost.dev/api/v1/get-post/${postGroupId}`, {
  headers: { 'x-api-key': 'YOUR_API_KEY' }
});
const data = await response.json();
const postedId = data.posts.find(p => p.platform === 'linkedin')?.postedId;

Rate Limiting

  • Analytics requests are subject to LinkedIn's rate limits
  • PostPost's caching helps reduce rate limit issues
  • For bulk analytics, add delays between requests

Error Reference

StatusErrorCauseSolution
400"postedId and platformId are required"Missing required fieldsInclude both fields
400"Invalid queryType"Unknown metric nameUse valid metric names
401"Invalid API key"Bad x-api-keyCheck API key
404"LinkedIn connection not found"Wrong platformIdVerify platformId
500"Failed to fetch LinkedIn post statistics"LinkedIn API errorPost may not exist or no data yet

On this page