How to Retrieve Connected Social Media Accounts with Publora
List all connected social media accounts for a user, including token health status and platform metadata.
Overview
The Publora API provides a single endpoint to retrieve all connected social media accounts. Each connection includes the platformId needed for creating posts, along with token expiration status and last posting activity.
API Reference
Endpoint:
GET https://api.publora.com/api/v1/platform-connectionsHeaders:
x-publora-key: YOUR_API_KEYSuccess Response (200):
{
"success": true,
"connections": [
{
"platformId": "twitter-123456789",
"username": "@yourhandle",
"displayName": "Your Name",
"profileImageUrl": "https://pbs.twimg.com/profile_images/...",
"profileUrl": null,
"accessTokenExpiresAt": null,
"tokenStatus": "valid",
"connectionStatus": "active",
"tokenExpiresIn": null,
"lastSuccessfulPost": "2026-02-20T14:30:00.000Z",
"lastError": null,
"subscriptionType": "None"
},
{
"platformId": "linkedin-Tz9W5i6ZYG",
"username": "John Doe",
"displayName": "John Doe",
"profileImageUrl": "https://media.licdn.com/...",
"profileUrl": "https://linkedin.com/in/johndoe",
"accessTokenExpiresAt": "2026-05-15T10:30:00.000Z",
"tokenStatus": "valid",
"connectionStatus": "active",
"tokenExpiresIn": "82d 4h",
"lastSuccessfulPost": "2026-02-22T09:15:00.000Z",
"lastError": null,
"subscriptionType": null
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
platformId |
string | Unique ID for creating posts (e.g., twitter-123456789) |
username |
string/null | Platform username or handle; null for a Telegram channel without a public username (use displayName) |
displayName |
string/null | Display name on the platform; null when the platform did not provide one |
profileImageUrl |
string/null | Profile image URL; null when not available |
profileUrl |
string/null | URL to profile page (null if unavailable) |
accessTokenExpiresAt |
string/null | Effective credential expiry — the date tokenStatus is derived from, not the raw access-token lifetime. null when no authoritative date exists: always for YouTube, and for the platforms that do not expire on a schedule. |
tokenStatus |
string | Credential expiry health: valid, expiring_soon, expired, unknown. Use it to warn ahead of expiry — do not compare accessTokenExpiresAt against the clock yourself. |
connectionStatus |
string | active or reconnect_required: whether Publora can publish with this connection now. Read this to decide about reconnecting. It also covers suspended accounts, whose tokenStatus can still be valid. |
tokenExpiresIn |
string/null | Human-readable time until expiration (e.g., "7d 3h") |
lastSuccessfulPost |
string/null | Timestamp of last successful post |
lastError |
object/null | Last posting error with message and occurredAt |
subscriptionType |
string/null | X only: None, Basic, Premium, or PremiumPlus, re-checked when publishing to X at most once a day; null on other platforms, and on X until a fetch succeeds |
Token Status Values
| Status | Meaning | Action Required |
|---|---|---|
valid |
Credential is usable. Facebook, Twitter/X, and Mastodon are treated as non-expiring; YouTube is always valid unless flagged for reconnection, since its token is refreshed on demand before each publish; TikTok's refresh credential is at least 30 days away; other expiring OAuth credentials have at least 7 days left. A Bluesky row with a stored username is reported as valid by this endpoint, which does not re-check the app password. | None |
expiring_soon |
TikTok refresh credential expires in under 30 days, or another expiring OAuth access token expires in under 7 days | Reconnect soon |
expired |
The effective expiry has passed, or Publora flagged the connection for reconnection after Facebook, Instagram, Threads, LinkedIn or TikTok rejected its credential — then accessTokenExpiresAt can still be null or in the future |
Reconnect required |
unknown |
Stored expiry data is missing or malformed; Telegram connections store no expiry date, so they always report unknown. For Bluesky in this endpoint, the projected connection has no username. Missing TikTok refreshTokenExpiresAt is valid. |
Check connectionStatus |
To decide whether to prompt a reconnect, read connectionStatus rather than this table: reconnect_required also flags X and YouTube accounts reported suspended, and Telegram connections are never flagged, so they stay active while their tokenStatus is unknown. See connectionStatus and tokenStatus.
Platform ID Formats
| Platform | Format | Example |
|---|---|---|
| X / Twitter | twitter-{id} |
twitter-123456789 |
linkedin-{id} |
linkedin-Tz9W5i6ZYG |
|
instagram-{id} |
instagram-17841412345678 |
|
| Threads | threads-{id} |
threads-17841412345678 |
| TikTok | tiktok-{id} |
tiktok-7123456789 |
| YouTube | youtube-{id} |
youtube-UCxxxxxxxxxxxx |
facebook-{id} |
facebook-112233445566 |
|
| Bluesky | bluesky-{did} |
bluesky-did:plc:abc123 |
| Mastodon | mastodon-{id} |
mastodon-109876543210 |
| Telegram | telegram-{id} |
telegram--1001234567890 |
Complete JavaScript Implementation
const PUBLORA_API_KEY = process.env.PUBLORA_API_KEY;
const BASE_URL = 'https://api.publora.com/api/v1';
/**
* Custom error class for Publora API errors
*/
class PubloraError extends Error {
constructor(message, statusCode, response) {
super(message);
this.name = 'PubloraError';
this.statusCode = statusCode;
this.response = response;
}
}
/**
* Retrieve all connected social media accounts
* @returns {Promise<Array>} Array of connection objects
* @throws {PubloraError} If the API request fails
*/
async function getConnectedAccounts() {
const response = await fetch(`${BASE_URL}/platform-connections`, {
headers: {
'x-publora-key': PUBLORA_API_KEY
}
});
// Handle non-JSON responses
let data;
try {
data = await response.json();
} catch (e) {
throw new PubloraError(
`Unexpected response: ${response.statusText}`,
response.status,
null
);
}
// Handle API errors
if (!response.ok) {
let errorMessage = data.error || `HTTP ${response.status}`;
if (response.status === 401) {
errorMessage = 'Invalid API key. Check your x-publora-key header.';
} else if (response.status === 500) {
errorMessage = 'Server error while fetching connections.';
}
throw new PubloraError(errorMessage, response.status, data);
}
return data.connections;
}
/**
* Get connections filtered by platform type
* @param {string} platform - Platform name (twitter, linkedin, instagram, etc.)
* @returns {Promise<Array>} Filtered connections
*/
async function getConnectionsByPlatform(platform) {
const connections = await getConnectedAccounts();
return connections.filter(c => c.platformId.startsWith(`${platform}-`));
}
/**
* Get all platform IDs for cross-platform posting
* @returns {Promise<string[]>} Array of platformIds
*/
async function getAllPlatformIds() {
const connections = await getConnectedAccounts();
return connections.map(c => c.platformId);
}
/**
* Check for expiring tokens and connections that need reconnecting
* @returns {Promise<{expiring: Array, expired: Array}>} Connections needing attention
*/
async function checkTokenHealth() {
const connections = await getConnectedAccounts();
const expiring = connections.filter(c => c.tokenStatus === 'expiring_soon');
// connectionStatus also catches suspended accounts, whose tokenStatus can still be 'valid'
const expired = connections.filter(c => c.connectionStatus === 'reconnect_required');
return { expiring, expired };
}
/**
* Build a map of platform type to connection IDs
* @returns {Promise<Object>} Map like { twitter: ["twitter-123"], linkedin: ["linkedin-ABC"] }
*/
async function getConnectionsByType() {
const connections = await getConnectedAccounts();
const platformMap = {};
for (const conn of connections) {
const platform = conn.platformId.split('-')[0];
if (!platformMap[platform]) {
platformMap[platform] = [];
}
platformMap[platform].push(conn.platformId);
}
return platformMap;
}
// ============ USAGE EXAMPLES ============
// Example 1: List all connections
async function listAllConnections() {
try {
const connections = await getConnectedAccounts();
console.log(`Found ${connections.length} connected accounts:`);
for (const conn of connections) {
const handle = conn.username ? ` (@${conn.username})` : ''; // null for private Telegram channels
console.log(` ${conn.platformId}: ${conn.displayName}${handle}`);
}
return connections;
} catch (error) {
console.error('Failed to fetch connections:', error.message);
throw error;
}
}
// Example 2: Get platform IDs for posting
async function prepareForCrossPost() {
try {
const platformIds = await getAllPlatformIds();
if (platformIds.length === 0) {
throw new Error('No connected accounts. Connect accounts in the Publora dashboard.');
}
console.log('Ready to post to:', platformIds.join(', '));
return platformIds;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
// Example 3: Monitor token expiry
async function monitorTokens() {
try {
const { expiring, expired } = await checkTokenHealth();
if (expired.length > 0) {
console.warn('RECONNECT REQUIRED:');
expired.forEach(c => console.warn(` ${c.platformId}`));
}
if (expiring.length > 0) {
console.warn('EXPIRING SOON - Reconnect recommended:');
expiring.forEach(c => console.warn(` ${c.platformId} - ${c.tokenExpiresIn}`));
}
if (expired.length === 0 && expiring.length === 0) {
console.log('All tokens healthy');
}
return { expiring, expired };
} catch (error) {
console.error('Error checking tokens:', error.message);
throw error;
}
}
// Example 4: Find accounts with recent errors
async function findAccountsWithErrors() {
const connections = await getConnectedAccounts();
return connections.filter(c => c.lastError !== null);
}
// Run examples
const connections = await listAllConnections();
const platformIds = await prepareForCrossPost();
await monitorTokens();Complete Python Implementation
import os
import requests
from typing import List, Dict, Optional
from dataclasses import dataclass
PUBLORA_API_KEY = os.environ.get('PUBLORA_API_KEY')
BASE_URL = 'https://api.publora.com/api/v1'
class PubloraError(Exception):
"""Custom exception for Publora 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
@dataclass
class TokenHealth:
"""Token health check results."""
expiring: List[Dict]
expired: List[Dict]
def get_connected_accounts() -> List[Dict]:
"""
Retrieve all connected social media accounts.
Returns:
list: Array of connection objects
Raises:
PubloraError: If the API request fails
"""
response = requests.get(
f'{BASE_URL}/platform-connections',
headers={'x-publora-key': PUBLORA_API_KEY}
)
# Handle non-JSON responses
try:
data = response.json()
except ValueError:
raise PubloraError(
f'Unexpected response: {response.text}',
response.status_code,
None
)
# Handle API errors
if not response.ok:
error_message = data.get('error', f'HTTP {response.status_code}')
if response.status_code == 401:
error_message = 'Invalid API key. Check your x-publora-key header.'
elif response.status_code == 500:
error_message = 'Server error while fetching connections.'
raise PubloraError(error_message, response.status_code, data)
return data['connections']
def get_connections_by_platform(platform: str) -> List[Dict]:
"""
Get connections filtered by platform type.
Args:
platform: Platform name (twitter, linkedin, instagram, etc.)
Returns:
list: Filtered connections
"""
connections = get_connected_accounts()
return [c for c in connections if c['platformId'].startswith(f'{platform}-')]
def get_all_platform_ids() -> List[str]:
"""
Get all platform IDs for cross-platform posting.
Returns:
list: Array of platformIds
"""
connections = get_connected_accounts()
return [c['platformId'] for c in connections]
def check_token_health() -> TokenHealth:
"""
Check for expiring tokens and connections that need reconnecting.
Returns:
TokenHealth: Connections needing attention
"""
connections = get_connected_accounts()
expiring = [c for c in connections if c.get('tokenStatus') == 'expiring_soon']
# connectionStatus also catches suspended accounts, whose tokenStatus can still be 'valid'
expired = [c for c in connections if c.get('connectionStatus') == 'reconnect_required']
return TokenHealth(expiring=expiring, expired=expired)
def get_connections_by_type() -> Dict[str, List[str]]:
"""
Build a map of platform type to connection IDs.
Returns:
dict: Map like { 'twitter': ['twitter-123'], 'linkedin': ['linkedin-ABC'] }
"""
connections = get_connected_accounts()
platform_map = {}
for conn in connections:
platform = conn['platformId'].split('-')[0]
if platform not in platform_map:
platform_map[platform] = []
platform_map[platform].append(conn['platformId'])
return platform_map
# ============ USAGE EXAMPLES ============
def list_all_connections():
"""List all connected accounts."""
try:
connections = get_connected_accounts()
print(f"Found {len(connections)} connected accounts:")
for conn in connections:
handle = f" (@{conn['username']})" if conn.get('username') else "" # None for private Telegram channels
print(f" {conn['platformId']}: {conn['displayName']}{handle}")
return connections
except PubloraError as e:
print(f"Failed to fetch connections: {e}")
raise
def prepare_for_cross_post() -> List[str]:
"""Get platform IDs for posting."""
try:
platform_ids = get_all_platform_ids()
if not platform_ids:
raise ValueError('No connected accounts. Connect accounts in the Publora dashboard.')
print(f"Ready to post to: {', '.join(platform_ids)}")
return platform_ids
except (PubloraError, ValueError) as e:
print(f"Error: {e}")
raise
def monitor_tokens():
"""Monitor token expiry."""
try:
health = check_token_health()
if health.expired:
print('RECONNECT REQUIRED:')
for c in health.expired:
print(f" {c['platformId']}")
if health.expiring:
print('EXPIRING SOON - Reconnect recommended:')
for c in health.expiring:
print(f" {c['platformId']} - {c.get('tokenExpiresIn', 'unknown')}")
if not health.expired and not health.expiring:
print('All tokens healthy')
return health
except PubloraError as e:
print(f"Error checking tokens: {e}")
raise
def find_accounts_with_errors() -> List[Dict]:
"""Find accounts with recent posting errors."""
connections = get_connected_accounts()
return [c for c in connections if c.get('lastError') is not None]
# Run examples
if __name__ == '__main__':
connections = list_all_connections()
platform_ids = prepare_for_cross_post()
monitor_tokens()cURL Examples
List all connections
curl https://api.publora.com/api/v1/platform-connections \
-H "x-publora-key: YOUR_API_KEY"With jq filtering
# Get just platform IDs
curl -s https://api.publora.com/api/v1/platform-connections \
-H "x-publora-key: YOUR_API_KEY" \
| jq -r '.connections[].platformId'
# Get Twitter accounts only
curl -s https://api.publora.com/api/v1/platform-connections \
-H "x-publora-key: YOUR_API_KEY" \
| jq '.connections | map(select(.platformId | startswith("twitter-")))'
# Check for connections that need reconnecting or expire soon
curl -s https://api.publora.com/api/v1/platform-connections \
-H "x-publora-key: YOUR_API_KEY" \
| jq '.connections | map(select(.connectionStatus == "reconnect_required" or .tokenStatus == "expiring_soon"))'Common Use Cases
Get platform IDs for creating a post
// Step 1: Get all platform IDs
const connections = await getConnectedAccounts();
const platformIds = connections.map(c => c.platformId);
// Step 2: Create a post to all platforms
const response = await fetch(`${BASE_URL}/create-post`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publora-key': PUBLORA_API_KEY
},
body: JSON.stringify({
content: 'Hello from all my social accounts!',
platforms: platformIds // Post to ALL connected accounts
})
});Post to specific platform types only
// Post only to Twitter and LinkedIn
const connections = await getConnectedAccounts();
const selectedPlatforms = connections
.filter(c => c.platformId.startsWith('twitter-') || c.platformId.startsWith('linkedin-'))
.map(c => c.platformId);
await createPost(content, selectedPlatforms);Validate connections before scheduling
async function validateBeforeScheduling(requiredPlatforms) {
const connections = await getConnectedAccounts();
const available = new Set(connections.map(c => c.platformId.split('-')[0]));
const missing = requiredPlatforms.filter(p => !available.has(p));
if (missing.length > 0) {
throw new Error(`Missing connections for: ${missing.join(', ')}`);
}
// Check for connections that cannot publish
const expired = connections.filter(c => c.connectionStatus === 'reconnect_required');
if (expired.length > 0) {
throw new Error(`Reconnect required: ${expired.map(c => c.platformId).join(', ')}`);
}
return true;
}
// Usage
await validateBeforeScheduling(['twitter', 'linkedin']);Error Reference
| Status | Error | Cause | Solution |
|---|---|---|---|
| 401 | "Invalid API key" |
Bad or missing x-publora-key |
Check API key |
| 500 | "Failed to fetch platform connections" |
Server error | Retry the request |
Important Notes
No Connections
If the response returns an empty connections array, the user needs to connect social accounts via the Publora dashboard at app.publora.com.
Token Expiration
OAuth tokens for LinkedIn, Instagram, Threads, and TikTok expire periodically. Check connections regularly: prompt users to reconnect when connectionStatus is reconnect_required, and warn them when tokenStatus is expiring_soon, before the token expires.
Rate Limiting
This endpoint has generous rate limits. For most use cases, you can call it as needed without throttling.
Related Guides
- Create Post - Create posts using platformIds
- Test Connection - Validate a connection before posting
- Workspace - Manage connections for multiple users