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:

NamespaceManages
client.social.brandsBrands: the container that owns connected accounts and posts
client.social.connectionsSocial accounts linked to a brand
client.social.postsIndividual posts: draft, schedule, publish, cancel, insights
client.social.bulkMany posts in one request, as an asynchronous job
client.social.limits()Connection cap and current usage

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:

Python
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.

Python
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,) -> APIObject
ArgumentTypeDescription
profile_namestrDisplay name of the brand. Required.
timezonedict{"label", "tzCode", "name", "utc"}. tzCode is the IANA zone that every schedule_date / schedule_time on this brand is read in. Required.
business_intentstrWhat the business does, used for content suggestions. Defaults to general.
categorieslist[str]Business categories. Defaults to an empty list.
site_urlstrThe brand's website. Setting it queues a read of the site.
logostrPublic https URL of the brand logo.
country_isostrTwo-letter ISO country code.
biostrShort brand bio.
brand_hashtagslist[str]Hashtags offered as suggestions in the composer.
competitor_hashtagslist[str]Competitor hashtags to track.
content_preferred_languagestrLanguage for generated content, e.g. en.
Python
result = 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 string

Brands 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.

Python
client.social.brands.list(    *,    statuses: list[str] | None = None,    profile_name: str | None = None,    page: int | None = None,    size: int | None = None,) -> APIObject
Python
result = 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.

Python
client.social.brands.retrieve(brand_id: str) -> APIObject
Python
brand = 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.

Python
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,) -> APIObject

categories, brand_hashtags and competitor_hashtags replace the stored list when sent; passing [] clears it.

Python
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.

Python
client.social.brands.archive(social_profile_id: str) -> APIObject
Python
result = client.social.brands.archive("6f2c9a41-7b0e-4d55-9a2f-3c8e1d47b902")print(result.status)  # ARCHIVED or TENURE_NOT_MET

A 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.


Mint a short-lived hosted URL that walks an end user through connecting one platform to a brand. Redirect them to url.

Python
client.social.connections.link(    brand_id: str,    *,    channel: str,    redirect_url: str,    error_url: str | None = None,    state: str | None = None,) -> APIObject
Python
link = 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.

Python
client.social.connections.list(brand_id: str) -> APIObject
Python
result = 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().

Python
client.social.connections.disconnect(social_profile_connected_channel_id: str) -> APIObject
Python
client.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.

Python
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,) -> APIObject
ArgumentTypeDescription
social_profile_idstrBrand UUID. Required.
namestrInternal name, not shown to the audience. Required.
platformslist[str]FACEBOOK, INSTAGRAM, TWITTER, LINKEDIN, PINTEREST. Each must already have a connection on the brand. Required.
contentstrThe post body. Required.
actionstrDRAFT, SCHEDULE or PUBLISH. Required.
media_urlslist[str]Public https image URLs. Required for Instagram and Pinterest.
video_urlslist[str]Public https video URLs.
linkstrLink to attach where the platform supports one.
scheduled_atstrISO-8601 with an offset. Alternative to the date/time pair.
schedule_datestrYYYY-MM-DD, read in the brand's timezone. Send with schedule_time.
schedule_timestrHH:MM, read in the brand's timezone. Send with schedule_date.
connected_channel_idslist[str]Restrict to specific connections.
client_referencestrYour own identifier, echoed back on bulk job results.

action="SCHEDULE" needs exactly one schedule form: scheduled_at, or schedule_date + schedule_time. Sending both is rejected with SY95046.

Python
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.

Python
client.social.posts.retrieve(post_id: str) -> APIObject
Python
post = 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.

Python
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,) -> APIObject

filters accepts name, platforms, status, start_date, end_date, social_connected_channel_id, approval_state, view_mode and filter_by_user.

Python
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.

Python
client.social.posts.cancel(social_post_id: str) -> APIObject
Python
client.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.

Python
client.social.posts.insights(    post_id: str,    *,    social_connected_channel_id: str,) -> APIObject
Python
import 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.

Python
client.social.bulk.create(    posts: list[dict],    *,    name: str | None = None,) -> APIObject

Each 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.

Python
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.id

social.bulk.retrieve

Poll a bulk job until its status is terminal.

Python
client.social.bulk.retrieve(bulk_job_id: str) -> APIObject
Python
job = 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.

Python
client.social.limits() -> APIObject
Python
limits = client.social.limits()print(limits.maxConnections, limits.usage.connections)     # pool vs usedprint(limits.brands)                                       # None — brands are unlimitedprint(limits.usage.brands)                                 # brands that exist, uncapped

Connections 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