Social
Manage brands, connect social accounts, and publish or schedule posts to Facebook, Instagram, X, LinkedIn and Pinterest via client.social.
All Social methods live under client.social, grouped into four namespaces plus
one top-level call:
IDs, platforms and statuses
Brand, post and connection IDs are plain UUID strings. Unlike location IDs they are never Base64-encoded, and the SDK does not transform them.
Platforms are the uppercase enum values FACEBOOK, INSTAGRAM, TWITTER
(which is X), LINKEDIN and PINTEREST. Post statuses are DRAFT, PENDING,
SCHEDULED, PUBLISHED, FAILED, ARCHIVED and REJECTED; per-channel
statuses are SCHEDULED, SUCCESS, ERROR and ARCHIVED.
Every write returns a success flag
Social writes report validation failures inside a 200 response, as
success: false with the reason in error. The SDK raises on transport errors
and on error payloads it recognises, but the habit worth keeping is to check
success yourself:
result = client.social.brands.create(profile_name="Jenny Home", timezone=tz)if not result.success: raise RuntimeError(result.error.message)Publishing is asynchronous
action="PUBLISH" accepts the post and returns its id. Poll
social.posts.retrieve() and read connectedChannels[] for the per-channel
status, liveLink and error; a post can succeed on one platform and fail on
another in the same call.
social.brands.create
Create a brand. Only profile_name and timezone are required.
client.social.brands.create( *, profile_name: str, timezone: dict, business_intent: str | None = None, categories: list[str] | None = None, site_url: str | None = None, logo: str | None = None, country_iso: str | None = None, bio: str | None = None, brand_hashtags: list[str] | None = None, competitor_hashtags: list[str] | None = None, content_preferred_language: str | None = None,) -> APIObjectresult = client.social.brands.create( profile_name="Jenny Home", timezone={ "label": "(GMT-05:00) Eastern Time", "tzCode": "America/New_York", "name": "Eastern Standard Time", "utc": "-05:00", }, business_intent="A family-run home furnishings store selling handmade furniture.", categories=["Furniture Store"], site_url="https://jennyhome.example",)brand_id = result.socialProfile.id # a plain UUID stringBrands are unlimited on API plans, so this never fails for a cap. What is
metered is connections — check social.limits() before minting a connection
link, not before creating a brand.
social.brands.list
List brands, offset-paginated. statuses is required inside the filter when you
send one.
client.social.brands.list( *, statuses: list[str] | None = None, profile_name: str | None = None, page: int | None = None, size: int | None = None,) -> APIObjectresult = client.social.brands.list(page=1, size=25)for brand in result.profiles: print(brand.id, brand.profileName, brand.connectedChannelCount)print(result.pageInfo.total, result.pageInfo.hasNextPage)social.brands.retrieve
Get one brand by its UUID.
client.social.brands.retrieve(brand_id: str) -> APIObjectbrand = client.social.brands.retrieve("6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902")print(brand.profileName, brand.timezone.tzCode)social.brands.update
Update a brand. Only profile_id is required; send just the fields you are
changing.
client.social.brands.update( profile_id: str, *, profile_name: str | None = None, timezone: dict | None = None, business_intent: str | None = None, categories: list[str] | None = None, site_url: str | None = None, logo: str | None = None, country_iso: str | None = None, bio: str | None = None, brand_hashtags: list[str] | None = None, competitor_hashtags: list[str] | None = None, content_preferred_language: str | None = None,) -> APIObjectcategories, brand_hashtags and competitor_hashtags replace the stored
list when sent; passing [] clears it.
client.social.brands.update( "6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902", profile_name="Jenny Home & Co.",)social.brands.archive
Archive a brand and its connections. The brand must be at least 90 days old.
client.social.brands.archive(social_profile_id: str) -> APIObjectresult = client.social.brands.archive("6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902")print(result.status) # ARCHIVED or TENURE_NOT_META brand younger than 90 days comes back with success: False and
status: "TENURE_NOT_MET" and nothing changes. Once tenure is met the archival
is immediate: the brand and every connection linked to it are archived in
the same call and stop publishing at once, but their connection slots stay held
until the end of the billing cycle, exactly as a manual disconnect would.
social.connections.link
Mint a short-lived hosted URL that walks an end user through connecting one
platform to a brand. Redirect them to url.
client.social.connections.link( brand_id: str, *, channel: str, redirect_url: str, error_url: str | None = None, state: str | None = None,) -> APIObjectlink = client.social.connections.link( "6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902", channel="INSTAGRAM", redirect_url="https://app.example.com/social/connected", error_url="https://app.example.com/social/connect-failed", state="sess_9f2c1e64",)print(link.url, link.expiresIn)The user returns to redirect_url with status, brandId, platform, your
state and, on success, connectedAccountId appended. The connection is
materialised a moment later, so poll social.connections.list() for a few
seconds rather than treating the first empty read as a failure. Instagram
requires a Business or Creator account linked to a Facebook Page.
social.connections.list
List the social accounts attached to a brand.
client.social.connections.list(brand_id: str) -> APIObjectresult = client.social.connections.list("6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902")for channel in result.socialConnected: print(channel.id, channel.platform, channel.displayName)print(result.channelCount)channel.id is the connection UUID every other call wants; referenceId is the
platform's own account identifier and is never accepted as input.
social.connections.disconnect
Remove a connection. One-way: restoring it means a fresh
social.connections.link().
client.social.connections.disconnect(social_profile_connected_channel_id: str) -> APIObjectclient.social.connections.disconnect("b81d5b3c-4a90-4f6a-9c11-71e0dd2fa4a7")Scheduled posts targeting the channel are not cancelled; they fail at publish
time with SY95045. Cancel them first with social.posts.cancel().
social.posts.create
Create one post for one brand across its connected channels.
client.social.posts.create( *, social_profile_id: str, name: str, platforms: list[str], content: str, action: str, media_urls: list[str] | None = None, video_urls: list[str] | None = None, link: str | None = None, scheduled_at: str | None = None, schedule_date: str | None = None, schedule_time: str | None = None, connected_channel_ids: list[str] | None = None, client_reference: str | None = None,) -> APIObjectaction="SCHEDULE" needs exactly one schedule form: scheduled_at, or
schedule_date + schedule_time. Sending both is rejected with SY95046.
result = client.social.posts.create( social_profile_id="6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902", name="Autumn collection launch", platforms=["INSTAGRAM", "FACEBOOK"], content="Our autumn collection lands today. Solid oak, hand-finished.", media_urls=["https://cdn.example.com/social/autumn.jpg"], action="SCHEDULE", schedule_date="2026-10-02", schedule_time="09:30",)print(result.success, result.socialPostId)social.posts.retrieve
Get one post with per-channel publish state. Poll this after a create.
client.social.posts.retrieve(post_id: str) -> APIObjectpost = client.social.posts.retrieve("c47a1e58-2b93-4f10-a6d7-58e0c9b12345")print(post.status)for channel in post.connectedChannels: print(channel.platform, channel.status, channel.liveLink, channel.error)social.posts.list
List a brand's posts, offset-paginated, with filters.
client.social.posts.list( brand_id: str, *, filters: dict | None = None, page: int | None = None, size: int | None = None, fetch_post_content: bool | None = None,) -> APIObjectfilters accepts name, platforms, status, start_date, end_date,
social_connected_channel_id, approval_state, view_mode and
filter_by_user.
result = client.social.posts.list( "6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902", filters={"status": ["SCHEDULED"], "startDate": "2026-10-01", "endDate": "2026-10-31"}, page=1, size=50,)for post in result.socialPosts: print(post.socialPostId, post.name, post.status)print(result.pageInfo.total)social.posts.cancel
Cancel a scheduled or draft post so it never publishes.
client.social.posts.cancel(social_post_id: str) -> APIObjectclient.social.posts.cancel("c47a1e58-2b93-4f10-a6d7-58e0c9b12345")Already-published posts cannot be cancelled and are not removed from any
platform; the call returns success: False for them.
social.posts.insights
Engagement and comments for one published post on one channel. Both fields come back as JSON strings carrying the platform's own payload.
client.social.posts.insights( post_id: str, *, social_connected_channel_id: str,) -> APIObjectimport json result = client.social.posts.insights( "c47a1e58-2b93-4f10-a6d7-58e0c9b12345", social_connected_channel_id="b81d5b3c-4a90-4f6a-9c11-71e0dd2fa4a7",)insights = json.loads(result.insights)comments = json.loads(result.comments)print(insights.get("impressions"), len(comments))The keys inside are the platform's and differ per platform; read them defensively.
social.bulk.create
Validate and queue up to 500 posts in one request. Rows may target different brands.
client.social.bulk.create( posts: list[dict], *, name: str | None = None,) -> APIObjectEach row takes the same fields as social.posts.create, in camelCase. If any
row is invalid, nothing is created: success is False, SY95050 comes back in
error, and job.results[] names each bad row by its 0-based index and its
clientReference.
result = client.social.bulk.create([ { "socialProfileId": "6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902", "name": "Autumn week 1", "platforms": ["INSTAGRAM", "FACEBOOK"], "content": "Week one of the autumn collection.", "mediaUrls": ["https://cdn.example.com/social/autumn-w1.jpg"], "action": "SCHEDULE", "scheduleDate": "2026-10-02", "scheduleTime": "09:30", "clientReference": "campaign-4471-slot-01", },])if not result.success: for row in result.job.results: print(row.index, row.clientReference, row.errors)else: job_id = result.job.idsocial.bulk.retrieve
Poll a bulk job until its status is terminal.
client.social.bulk.retrieve(bulk_job_id: str) -> APIObjectjob = client.social.bulk.retrieve("9b3f18d0-7a26-4c51-8e94-1f0d5c6b7a82")print(job.status, job.processedCount, "/", job.totalCount)for row in job.results: print(row.clientReference, row.status, row.socialPostId)status is QUEUED, PROCESSING, COMPLETED, COMPLETED_WITH_ERRORS or
FAILED. A row reaching CREATED means the post exists, not that it published:
take socialPostId to social.posts.retrieve() for the per-channel outcome.
social.limits
Connection cap and current usage, in one call.
client.social.limits() -> APIObjectlimits = client.social.limits()print(limits.maxConnections, limits.usage.connections) # pool vs usedprint(limits.brands) # None — brands are unlimitedprint(limits.usage.brands) # brands that exist, uncappedConnections are an account-wide pool — 20 on Social Launch, 125 on Social
Growth, plus 5 for every $25/mo connection pack — not a per-brand allowance.
Check it before minting a connection link so you can show an upgrade path
instead of an SY95042. limits.brands is None on every API plan.
See also
- Plans and add-ons: tiers, add-on prices, the connection pool, and the 90-day tenure rule
- Social REST reference: the raw endpoints these methods wrap
- Posts: the location-level product, a different subscription
- Error handling: typed exceptions and the
successflag