Profiles API Reference
The Profiles API allows you to retrieve and manage connected social media profiles. Profiles represent authenticated connections to social media platforms.
Endpoints
Section titled “Endpoints”| Method | Endpoint | Description |
|---|---|---|
GET | /api/profiles | List all profiles |
GET | /api/profiles/:id | Get a single profile (with latest stats) |
GET | /api/profiles/:id/placements | List placements for a profile |
GET | /api/profiles/:id/stats | Get profile stats timeseries |
DELETE | /api/profiles/:id | Delete/disconnect a profile |
Profile object
Section titled “Profile object”A profile represents a connected social media account.
| Field | Type | Description |
|---|---|---|
id | string | Unique profile identifier (id) |
name | string | Display name of the connected account |
status | string | Platform connection status: active, expired, inactive (might be disconnected or suspended on a platform) |
platform | string | Platform identifier |
profile_group_id | string | ID of the profile group this belongs to |
expires_at | string|null | ISO 8601 timestamp when the connection expires (if applicable) |
post_count | integer | Number of posts made through this profile |
avatar_url | string|null | URL to the profile’s avatar image (resized, hosted by Postproxy). null if not yet downloaded |
Platform values
Section titled “Platform values”| Platform | Account type |
|---|---|
facebook | Facebook Page |
instagram | Instagram Business/Creator Account |
tiktok | TikTok Account |
linkedin | LinkedIn Profile or Company Page |
youtube | YouTube Channel |
twitter | X (Twitter) Account |
threads | Threads Account |
pinterest | Pinterest Account |
bluesky | Bluesky Account |
telegram | Telegram Bot (publishes to channels via placements) |
google_business | Google Business Profile (publishes to locations via placements) |
List profiles
Section titled “List profiles”GET /api/profiles
Retrieves your profiles. Pass profile_group_id to restrict to a single group; omit it to return profiles across every group your API key or OAuth user can access.
Query parameters
Section titled “Query parameters”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
profile_group_id | string | No | - | Restrict to profiles in this group. When omitted, returns profiles across every profile group your API key or OAuth user can access. |
Example
Section titled “Example”curl -X GET "https://api.postproxy.dev/api/profiles" \ -H "Authorization: Bearer YOUR_API_KEY"import PostProxy from "postproxy-sdk";
const client = new PostProxy("YOUR_API_KEY");const profiles = await client.profiles.list();console.log(profiles);from postproxy import PostProxy
client = PostProxy("YOUR_API_KEY")profiles = await client.profiles.list()print(profiles)package main
import ( "fmt" postproxy "github.com/postproxy/postproxy-go")
func main() { client := postproxy.New("YOUR_API_KEY") profiles, _ := client.Profiles.List() fmt.Println(profiles)}require "postproxy"
client = PostProxy::Client.new("YOUR_API_KEY")profiles = client.profiles.listputs profilesuse PostProxy\PostProxy;
$client = new PostProxy("YOUR_API_KEY");$profiles = $client->profiles->list();print_r($profiles);import dev.postproxy.PostProxy;
PostProxy client = new PostProxy("YOUR_API_KEY");var profiles = client.profiles().list();System.out.println(profiles);using PostProxy;
var client = new PostProxyClient("YOUR_API_KEY");var profiles = await client.Profiles.ListAsync();Console.WriteLine(profiles);Response:
{ "data": [ { "id": "prof123abc", "name": "My Company Page", "platform": "facebook", "status": "active", "profile_group_id": "grp456xyz", "expires_at": null, "post_count": 42, "avatar_url": "https://cdn.postproxy.dev/uploads/avatar_prof123abc.jpg" }, { "id": "prof789def", "name": "@mycompany", "platform": "instagram", "status": "expired", "profile_group_id": "grp456xyz", "expires_at": "2024-03-15T00:00:00.000Z", "post_count": 38, "avatar_url": "https://cdn.postproxy.dev/uploads/avatar_prof789def.jpg" }, { "id": "prof321ghi", "name": "John Doe", "platform": "linkedin", "status": "inactive", "profile_group_id": "grp456xyz", "expires_at": null, "post_count": 15, "avatar_url": null }, { "id": "prof654jkl", "name": "@mycompany", "platform": "twitter", "status": "active", "profile_group_id": "grp456xyz", "expires_at": null, "post_count": 127, "avatar_url": "https://cdn.postproxy.dev/uploads/avatar_prof654jkl.jpg" } ]}Profile resolution
Section titled “Profile resolution”The same scoping rule applies to the :id endpoints below (GET /api/profiles/:id, /placements, /stats, and DELETE /api/profiles/:id):
profile_group_id sent? | Profile ID lookup scope |
|---|---|
| Yes | The profile must belong to that group |
| No | Resolved across every profile group your API key or OAuth user can access |
This mirrors the resolution rule already used by POST /api/posts.
Get profile
Section titled “Get profile”GET /api/profiles/:id
Retrieves a single profile by its ID. The response includes the profile fields plus the latest stats snapshot per placement and (for placement networks) a summary_stats rollup.
For non-placement networks (e.g. bluesky, twitter), latest_stats contains a single entry with placement_id: null and summary_stats is null.
Snapshots are typically refreshed every 23 hours per profile. If latest_stats is empty, the profile has been connected but has not yet been polled for stats.
Path parameters
Section titled “Path parameters”| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Profile id |
Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
latest_stats | array | Latest snapshot per placement. One entry for non-placement networks (with placement_id: null). Empty array if no snapshots have been recorded yet. |
latest_stats[].placement_id | string|null | Platform-specific placement ID. null for non-placement networks. |
latest_stats[].stats | object | Platform-specific metrics. See Stats fields by network. |
latest_stats[].recorded_at | string | ISO 8601 timestamp when the snapshot was captured. |
summary_stats | object|null | For placement networks: numeric values summed across the latest snapshot of every placement. null for non-placement networks and when no snapshots exist. Non-numeric values (e.g. channel_title) are omitted from the summary. |
summary_stats.stats | object | Summed metrics. |
summary_stats.recorded_at | string | ISO 8601 timestamp of the most recent placement snapshot included in the summary. |
Example
Section titled “Example”curl -X GET "https://api.postproxy.dev/api/profiles/prof123abc" \ -H "Authorization: Bearer YOUR_API_KEY"import PostProxy from "postproxy-sdk";
const client = new PostProxy("YOUR_API_KEY");const profile = await client.profiles.get("prof123abc");console.log(profile);from postproxy import PostProxy
client = PostProxy("YOUR_API_KEY")profile = await client.profiles.get("prof123abc")print(profile)package main
import ( "fmt" postproxy "github.com/postproxy/postproxy-go")
func main() { client := postproxy.New("YOUR_API_KEY") profile, _ := client.Profiles.Get("prof123abc") fmt.Println(profile)}require "postproxy"
client = PostProxy::Client.new("YOUR_API_KEY")profile = client.profiles.get("prof123abc")puts profileuse PostProxy\PostProxy;
$client = new PostProxy("YOUR_API_KEY");$profile = $client->profiles->get("prof123abc");print_r($profile);import dev.postproxy.PostProxy;
PostProxy client = new PostProxy("YOUR_API_KEY");var profile = client.profiles().get("prof123abc");System.out.println(profile);using PostProxy;
var client = new PostProxyClient("YOUR_API_KEY");var profile = await client.Profiles.GetAsync("prof123abc");Console.WriteLine(profile);Response:
{ "id": "prof_li_001", "name": "Acme Inc", "platform": "linkedin", "status": "active", "profile_group_id": "grp456xyz", "expires_at": null, "post_count": 42, "avatar_url": "https://cdn.postproxy.dev/uploads/avatar_prof_li_001.jpg", "latest_stats": [ { "placement_id": "108520199", "stats": { "followerCount": 4567, "shareCount": 10, "likeCount": 99, "allPageViews": 12728, "overviewPageViews": 6348, "aboutPageViews": 1533, "careersPageViews": 1378, "peoplePageViews": 3370, "insightsPageViews": 99 }, "recorded_at": "2026-05-11T08:00:00Z" }, { "placement_id": "110131347", "stats": { "followerCount": 1200, "shareCount": 4, "likeCount": 22, "allPageViews": 3100 }, "recorded_at": "2026-05-11T08:00:01Z" } ], "summary_stats": { "stats": { "followerCount": 5767, "shareCount": 14, "likeCount": 121, "allPageViews": 15828, "overviewPageViews": 6348, "aboutPageViews": 1533, "careersPageViews": 1378, "peoplePageViews": 3370, "insightsPageViews": 99 }, "recorded_at": "2026-05-11T08:00:01Z" }}List placements
Section titled “List placements”GET /api/profiles/:id/placements
Retrieves the available placements for a profile. For Facebook profiles, placements are business pages. For LinkedIn profiles, placements include the personal profile and organizations. For Pinterest profiles, placements are boards. For Telegram profiles, placements are the channels the bot has been added to. For Google Business profiles, placements are the locations associated with the connected Business Profile account(s).
This endpoint is available for facebook, linkedin, pinterest, telegram, and google_business profiles.
If no placement is specified when creating a post:
- LinkedIn: defaults to the personal profile
- Facebook: it fails —
page_idis always required - Pinterest: it fails
- Telegram: it fails —
chat_idis always required - Google Business: it fails —
location_id(the location resource path) is always required
For Telegram, each placement is a channel the bot has been added to. The placement id is the Telegram chat_id you pass as chat_id when creating a post. The list is empty until the user adds the bot as administrator to a channel — Telegram pushes a my_chat_member event for each one and we record it. Poll this endpoint after connecting Telegram until the expected channels appear.
For Google Business, each placement is a location resource managed by the connected account(s). The placement id is the full Business Profile resource path (e.g. accounts/123456789/locations/987654321) you pass as location_id when creating a post. Listing locations issues one call per Google Business account on the profile, so responses may be slower than other networks when many accounts/locations are linked.
Path parameters
Section titled “Path parameters”| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Profile id |
Placement object
Section titled “Placement object”| Field | Type | Description |
|---|---|---|
id | string|null | Platform-specific placement ID. null for personal profile (LinkedIn) |
name | string | Display name of the placement |
Example
Section titled “Example”curl -X GET "https://api.postproxy.dev/api/profiles/prof123abc/placements" \ -H "Authorization: Bearer YOUR_API_KEY"import PostProxy from "postproxy-sdk";
const client = new PostProxy("YOUR_API_KEY");const placements = await client.profiles.placements("prof123abc");console.log(placements);from postproxy import PostProxy
client = PostProxy("YOUR_API_KEY")placements = await client.profiles.placements("prof123abc")print(placements)package main
import ( "fmt" postproxy "github.com/postproxy/postproxy-go")
func main() { client := postproxy.New("YOUR_API_KEY") placements, _ := client.Profiles.Placements("prof123abc") fmt.Println(placements)}require "postproxy"
client = PostProxy::Client.new("YOUR_API_KEY")placements = client.profiles.placements("prof123abc")puts placementsuse PostProxy\PostProxy;
$client = new PostProxy("YOUR_API_KEY");$placements = $client->profiles->placements("prof123abc");print_r($placements);import dev.postproxy.PostProxy;
PostProxy client = new PostProxy("YOUR_API_KEY");var placements = client.profiles().placements("prof123abc");System.out.println(placements);using PostProxy;
var client = new PostProxyClient("YOUR_API_KEY");var placements = await client.Profiles.PlacementsAsync("prof123abc");Console.WriteLine(placements);Response:
{ "data": [ { "id": null, "name": "Personal Profile" }, { "id": "108520199", "name": "Acme Marketing" }, { "id": "110131347", "name": "Acme Labs" } ]}Profile stats
Section titled “Profile stats”GET /api/profiles/:id/stats
Retrieves the full stats timeseries for a profile. Mirrors Post Stats in shape (records[].stats + recorded_at) — use this to plot follower growth and engagement trends over time.
Snapshots are captured roughly every 23 hours. For networks with multiple placements (Facebook pages, LinkedIn organizations, Telegram channels), each placement has its own timeseries — placement_id is required so the response is scoped to a single placement.
Path parameters
Section titled “Path parameters”| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Profile id |
Query parameters
Section titled “Query parameters”| Name | Type | Required | Description |
|---|---|---|---|
placement_id | string | Conditional | Required for facebook, linkedin, and telegram profiles. The platform-specific ID returned by List placements. Omit (or ignored) for other networks. |
from | string | No | ISO 8601 timestamp — only include snapshots recorded at or after this time. |
to | string | No | ISO 8601 timestamp — only include snapshots recorded at or before this time. |
Example
Section titled “Example”curl -X GET "https://api.postproxy.dev/api/profiles/prof_li_001/stats?placement_id=108520199&from=2026-04-01T00:00:00Z" \ -H "Authorization: Bearer YOUR_API_KEY"import { PostProxy } from "postproxy-sdk";
const client = new PostProxy("YOUR_API_KEY");const stats = await client.profiles.getProfileStats("prof_li_001", { placementId: "108520199", from: "2026-04-01T00:00:00Z",});console.log(stats);from postproxy import PostProxy
client = PostProxy("YOUR_API_KEY")stats = await client.profiles.get_profile_stats( "prof_li_001", placement_id="108520199", from_="2026-04-01T00:00:00Z",)print(stats)package main
import ( "context" "fmt" "github.com/postproxy/postproxy-go")
func main() { client := postproxy.NewClient("YOUR_API_KEY") placementID := "108520199" from := "2026-04-01T00:00:00Z" stats, _ := client.Profiles.GetProfileStats(context.Background(), "prof_li_001", &postproxy.ProfileStatsOptions{ PlacementID: &placementID, From: &from, }) fmt.Println(stats)}require "postproxy"
client = PostProxy::Client.new("YOUR_API_KEY")stats = client.profiles.get_profile_stats( "prof_li_001", placement_id: "108520199", from: "2026-04-01T00:00:00Z")puts statsuse PostProxy\Client;
$client = new Client(apiKey: 'YOUR_API_KEY');$stats = $client->profiles()->getProfileStats( 'prof_li_001', placementId: '108520199', from: '2026-04-01T00:00:00Z',);print_r($stats);import dev.postproxy.sdk.PostProxy;
var client = PostProxy.builder("YOUR_API_KEY").build();var stats = client.profiles().getProfileStats( "prof_li_001", "108520199", "2026-04-01T00:00:00Z", null);System.out.println(stats);using PostProxy;
var client = PostProxyClient.Builder("YOUR_API_KEY").Build();var stats = await client.Profiles.GetProfileStatsAsync( "prof_li_001", placementId: "108520199", from: "2026-04-01T00:00:00Z");Console.WriteLine(stats);Response:
{ "data": { "profile_id": "prof_li_001", "platform": "linkedin", "placement_id": "108520199", "records": [ { "stats": { "followerCount": 4500, "shareCount": 8, "likeCount": 80, "allPageViews": 12000 }, "recorded_at": "2026-05-09T08:00:00Z" }, { "stats": { "followerCount": 4520, "shareCount": 9, "likeCount": 90, "allPageViews": 12400 }, "recorded_at": "2026-05-10T08:00:00Z" }, { "stats": { "followerCount": 4567, "shareCount": 10, "likeCount": 99, "allPageViews": 12728 }, "recorded_at": "2026-05-11T08:00:00Z" } ] }}Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
data.profile_id | string | Profile ID. |
data.platform | string | Network name (facebook, linkedin, bluesky, etc.). |
data.placement_id | string|null | The placement filter that was applied (echo of the request). null for non-placement networks. |
data.records | array | Snapshots ordered by recorded_at ascending. |
records[].stats | object | Platform-specific metrics. See Stats fields by network. |
records[].recorded_at | string | ISO 8601 timestamp when the snapshot was captured. |
Error responses
Section titled “Error responses”Missing placement_id for a placement network (400):
{ "error": "placement_id is required for linkedin profiles"}Profile not found (404):
{ "error": "Not found"}Stats fields by network
Section titled “Stats fields by network”The stats object’s keys come straight from each platform’s API — they are not normalized into a common schema, so each network exposes a different set.
| Network | Placement-scoped? | Typical fields |
|---|---|---|
facebook | Yes (per page) | fan_count, followers_count, plus daily page insights (e.g. page_impressions, page_views_total, page_fan_adds, page_fan_removes) |
linkedin | Yes (per organization) | followerCount, shareCount, likeCount, commentCount, clickCount, engagement, allPageViews, overviewPageViews, aboutPageViews, careersPageViews, peoplePageViews, insightsPageViews |
telegram | Yes (per channel) | followers_count, channel_title, channel_username |
instagram | No | followers_count, follows_count, media_count, plus per-window insights suffixed with _1d, _7d, _14d, _30d: reach_*, profile_views_*, accounts_engaged_*, total_interactions_*, website_clicks_*, follower_count_* |
threads | No | followers_count, views, likes, replies, reposts, quotes |
youtube | No | subscriberCount, viewCount, videoCount |
twitter | No | followers_count, following_count, tweet_count, listed_count, like_count |
tiktok | No | follower_count, following_count, likes_count, video_count |
pinterest | No | follower_count, following_count, pin_count, board_count, monthly_views, analytics_30d (nested 30-day rollup) |
bluesky | No | followersCount, followsCount, postsCount |
Notes:
- LinkedIn page-view metrics are filtered down to the rollups (we drop redundant mobile/desktop splits and dead sections like
productsPageViews/lifeAtPageViews). - Non-numeric fields (e.g. Telegram’s
channel_title) appear inlatest_stats[].statsbut are omitted fromsummary_stats.stats, which sums numeric values only. - A stats key only appears in a snapshot if the platform returned a value for it on that polling cycle, so fields can come and go between records.
Delete profile
Section titled “Delete profile”DELETE /api/profiles/:id
Disconnects and removes a profile from the account. This does not affect posts already published through this profile.
Path parameters
Section titled “Path parameters”| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Profile id |
Example
Section titled “Example”curl -X DELETE "https://api.postproxy.dev/api/profiles/prof123abc" \ -H "Authorization: Bearer YOUR_API_KEY"import PostProxy from "postproxy-sdk";
const client = new PostProxy("YOUR_API_KEY");const result = await client.profiles.delete("prof123abc");console.log(result);from postproxy import PostProxy
client = PostProxy("YOUR_API_KEY")result = await client.profiles.delete("prof123abc")print(result)package main
import ( "fmt" postproxy "github.com/postproxy/postproxy-go")
func main() { client := postproxy.New("YOUR_API_KEY") result, _ := client.Profiles.Delete("prof123abc") fmt.Println(result)}require "postproxy"
client = PostProxy::Client.new("YOUR_API_KEY")result = client.profiles.delete("prof123abc")puts resultuse PostProxy\PostProxy;
$client = new PostProxy("YOUR_API_KEY");$result = $client->profiles->delete("prof123abc");print_r($result);import dev.postproxy.PostProxy;
PostProxy client = new PostProxy("YOUR_API_KEY");var result = client.profiles().delete("prof123abc");System.out.println(result);using PostProxy;
var client = new PostProxyClient("YOUR_API_KEY");var result = await client.Profiles.DeleteAsync("prof123abc");Console.WriteLine(result);Response:
{ "success": true}Token expiration
Section titled “Token expiration”Some platforms issue access tokens that expire. The expires_at field indicates when the connection will expire and require re-authentication.
| Behavior | Description |
|---|---|
expires_at: null | Token does not expire or has a refresh token |
expires_at: "2024-..." | Token expires at the specified time |
When a token expires:
- Posts to that profile will fail
- The user needs to reconnect the profile through the web dashboard
- Use the Initialize Connection endpoint to generate a new connection URL
Connecting new profiles
Section titled “Connecting new profiles”Profiles cannot be created directly via the API. To connect a new social media account:
- Use the Initialize Connection endpoint to get an OAuth URL
- Redirect the user to that URL to authenticate
- User is redirected back to your
redirect_urlafter authentication - The profile is automatically created and associated with the profile group
Using profiles in posts
Section titled “Using profiles in posts”When creating posts, reference profiles by:
- Profile ID: Use the
idid directly - Platform name: Use the platform string (e.g.,
"twitter") to automatically select the profile for that platform
{ "profiles": ["prof123abc", "twitter", "linkedin"]}If multiple profiles exist for the same platform in a profile group, using the platform name selects the first one. Use the profile ID for explicit selection.