Social Planner
Social Planner schedules and manages social media posts across the accounts connected to your SMBcrm account/location. Use it to connect accounts, create and schedule posts, bulk-schedule from a CSV file, apply watermarks, work with comments, and read account statistics.
Base URL: https://services.smbcrm.com · Version header: v3 ·
Scopes: each endpoint below lists the scope it needs, and the table that follows
collects them. See Scopes.
| Scope | Grants |
|---|---|
socialplanner/account.readonly |
List connected accounts and validate Community members. |
socialplanner/account.write |
Disconnect an account. |
socialplanner/oauth.readonly |
Start the connect flow, list accounts you can connect, and list Community members. |
socialplanner/oauth.write |
Connect an account. |
socialplanner/post.readonly |
Search and retrieve posts. |
socialplanner/post.write |
Create, update, and delete posts. |
socialplanner/category.readonly |
Read categories and category queues. |
socialplanner/category.write |
Create and change category queues. |
socialplanner/tag.readonly |
List tags. |
socialplanner/watermarks.readonly |
Read watermark templates. |
socialplanner/watermarks.write |
Create, update, and delete watermark templates, and apply a watermark to an image. |
socialplanner/csv.readonly |
Read CSV import status. |
socialplanner/csv.write |
Upload and manage CSV imports. |
socialplanner/comments.readonly |
List comments. |
socialplanner/comments.write |
Create comments and like or unlike them. |
socialplanner/statistics.readonly |
Read account statistics. |
Connected accounts
Section titled “Connected accounts”curl https://services.smbcrm.com/social-media-posting/<location_id>/accounts \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Accounts", "results": { "accounts": [ { "id": "<social_account_id>", "oauthId": "<oauth_id>", "profileId": "<profile_id>", "name": "Acme Roofing", "platform": "facebook", "type": "page", "expire": "2026-12-01T00:00:00.000Z", "isExpired": false, "active": true, "locationId": "<location_id>", "connectionSource": "social_media_posting" }, { "id": "<social_account_id>", "oauthId": "<oauth_id>", "profileId": "<profile_id>", "name": "@acmeroofing", "platform": "instagram", "type": "profile", "expire": "2026-12-01T00:00:00.000Z", "isExpired": false, "active": true, "locationId": "<location_id>", "connectionSource": "social_media_posting" } ], "groups": [ { "id": "<group_id>", "name": "Primary", "accountIds": ["<social_account_id>", "<social_account_id>"] } ], "total": 2 }}Use the id values from this response as accountIds when creating a post. The endpoint
isn’t paginated: total is the number of accounts returned.
| Account field | Description |
|---|---|
id |
Account ID. |
oauthId |
The OAuth provider’s identifier for the account. |
profileId |
The profile identifier on the social platform. |
name |
Display name of the account. |
platform |
The platform, such as google, facebook, instagram, linkedin, or tiktok. |
type |
Account type, for example location, page, or profile. |
expire |
When the account’s token expires. |
isExpired |
true when the token has expired. |
active |
The raw connection flag. Unlike isExpired, it doesn’t account for token expiry. |
connectionSource |
Which service the account was connected through: integration or social_media_posting. |
locationId |
The sub-account the connected account belongs to. |
meta |
Additional account metadata. |
facebookIgnoreMessages |
Always true. You can ignore it. |
groups lists your account groups. Each has an id, a name, and the accountIds it
contains.
Disconnect an account
Section titled “Disconnect an account”The account is also removed from any group it belongs to. Add the optional userId query
parameter to record which user disconnected it. The value is stored for auditing only, and
the account is disconnected without it.
curl -X DELETE "https://services.smbcrm.com/social-media-posting/<location_id>/accounts/<social_account_id>?userId=<user_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Deleted Account", "results": { "locationId": "<location_id>", "id": "<social_account_id>" }}Connect an account
Section titled “Connect an account”Connecting an account takes three steps and works for facebook, instagram, google,
linkedin, tiktok, youtube, pinterest, and threads. Step 1 opens the platform’s
sign-in screen, so it runs in a browser window with a person present. Steps 2 and 3 are API
calls you make from your server.
Open this URL in a browser window, not with curl. locationId and userId are required
query parameters. After the user signs in, the window posts a message to your page. The
message carries the accountId you need for step 2, and reconnectAccounts, which lists
accounts that need reconnecting.
window.addEventListener('message', (event) => { if (event.data && event.data.accountId) { const { accountId, platform, reconnectAccounts } = event.data; // Pass accountId to step 2 }});{accountId} is the accountId from step 1. Add search to filter the results by name.
Depending on platform, the response lists Facebook Pages, Instagram professional
accounts, Google Business Profile locations, LinkedIn pages and profiles, TikTok creator
accounts, YouTube channels, Pinterest business accounts, or Threads profiles.
curl "https://services.smbcrm.com/social-media-posting/oauth/<location_id>/facebook/accounts/<oauth_account_id>?search=roofing" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"Most platforms take the originId, name, and optional avatar of the account you picked
in step 2. These platforms also take or require:
| Platform | Additional fields |
|---|---|
facebook |
type: page, group, profile, location, or business. |
instagram |
pageId (required): the ID of the Facebook Page linked to the Instagram account. |
linkedin |
type: page or profile. urn: the LinkedIn URN. |
google |
Send location (name and title required) and account (name, accountName, type, verificationState, and vettedState required) objects instead of originId, name, and avatar. |
pinterest |
verified, username, and websiteUrl are required along with originId and name. |
threads |
type: profile. |
youtube |
type: profile. verified and username. |
tiktok |
type: profile or business. verified, username, and connectionSource (integration or social_media_posting). |
curl -X POST https://services.smbcrm.com/social-media-posting/oauth/<location_id>/facebook/accounts/<oauth_account_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "type": "page", "originId": "<page_origin_id>", "name": "Acme Roofing", "avatar": "https://cdn.example.com/avatar-1.png" }'{ "success": true, "statusCode": 201, "message": "Added Facebook Account", "results": { "_id": "<social_account_id>", "oAuthId": "<oauth_id>", "locationId": "<location_id>", "platform": "facebook", "type": "page", "name": "Acme Roofing", "active": true }}Search posts
Section titled “Search posts”All body fields are optional. skip and limit are sent as numeric strings, not numbers: a
JSON number is rejected. fromDate and toDate are ISO 8601 date-time strings, and
includeUsers is the string "true" or "false". A successful search returns 201 Created.
See Pagination.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/posts/list \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "type": "scheduled", "accounts": "<social_account_id>", "skip": "0", "limit": "20", "fromDate": "2026-10-01T00:00:00.000Z", "toDate": "2026-10-31T23:59:59.999Z", "includeUsers": "false" }'{ "success": true, "statusCode": 201, "message": "Fetched Posts", "results": { "posts": [ { "_id": "<post_id>", "locationId": "<location_id>", "source": "composer", "accountIds": ["<social_account_id>"], "type": "post", "summary": "Fall is here. 10% off roof inspections this week.", "status": "scheduled", "displayDate": "2026-10-20T14:00:00.000Z", "createdBy": "<user_id>", "createdAt": "2026-10-08T15:04:00.000Z" } ], "count": 1 }}| Body field | Type | Description |
|---|---|---|
type |
string | Filter by post status: recent, all, scheduled, draft, failed, in_review, published, in_progress, pending, or deleted. Default all. |
postType |
string | Filter by format: post, story, or reel. Matches the type the post was created with. |
accounts |
string | Account IDs in one comma-separated string. |
skip |
string | Number of records to skip. Default "0". |
limit |
string | Maximum number of records to return. Default "10". |
fromDate |
string | Start of the date range, as an ISO 8601 date-time. |
toDate |
string | End of the date range, as an ISO 8601 date-time. |
includeUsers |
string | "true" to include user data with each post. |
Retrieve a post
Section titled “Retrieve a post”{postId} is the _id of the post. The endpoint also accepts the platform’s own 24-character
hexadecimal post ID.
curl https://services.smbcrm.com/social-media-posting/<location_id>/posts/<post_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Post", "results": { "post": { "_id": "<post_id>", "locationId": "<location_id>", "source": "composer", "accountIds": ["<social_account_id>"], "type": "post", "summary": "Fall is here. 10% off roof inspections this week.", "media": [{ "url": "https://cdn.example.com/fall-promo.jpg", "type": "image/jpeg" }], "status": "scheduled", "displayDate": "2026-10-20T14:00:00.000Z", "createdBy": "<user_id>", "createdAt": "2026-10-08T15:04:00.000Z" } }}The post object
Section titled “The post object”Search, retrieve, and create all return posts in the same shape. The _id is the value to use
as {postId} in the retrieve, update, and delete endpoints. To follow a post through
publishing, search or retrieve it again and watch status for published or failed.
| Field | Description |
|---|---|
_id |
Post ID. |
locationId |
The SMBcrm account/location the post belongs to. |
source |
Where the post came from: composer, csv, recurring, review, rss, template-library, category-queue, or native. |
accountIds |
The connected accounts the post targets. |
type |
post, story, reel, or short. |
status |
draft, scheduled, in_review, in_progress, pending, published, failed, notification_sent, or deleted. |
summary |
The caption text. |
media |
Media items. Each has a url and a MIME type, and can include thumbnail, defaultThumb, and altText. |
displayDate |
The post’s date, as an ISO 8601 date-time. Responses don’t include scheduleDate. |
publishedAt |
When the post was published. |
previewLink |
Link to the published post on the platform. null until the post is published, and for platforms that don’t return a link. |
postId |
The post’s identifier on the social platform. |
error |
The error text when publishing failed. |
insights |
like, share, and comment counts for a published post. Filled in asynchronously for Facebook, Instagram, LinkedIn, and YouTube. |
tags |
The _id values of the post’s tags. |
createdBy, createdAt, updatedAt |
Who created the post, and when it was created and last updated. |
Posts can also carry postApprovalDetails, ogTagsDetails, and the platform-specific detail
objects described under Create and schedule a post.
Create & schedule a post
Section titled “Create & schedule a post”One request creates one post shared by every account in accountIds. All of those accounts
get the same summary and media. The caption is trimmed to the shortest limit among the
selected platforms, so adding a Bluesky account trims the caption to 300 characters for
Facebook, Instagram, and LinkedIn too. Media is capped per platform when the post publishes
and isn’t reduced to the shortest limit. To keep full-length, platform-specific captions,
send one request per platform.
Caption limits: Facebook 63,206, LinkedIn 3,000, Instagram and TikTok 2,200, Google 1,500, Pinterest 800, Threads 500, Bluesky 300.
type is required. It sets the post format: post, story, or reel. accountIds (a
non-empty array of connected account IDs) is required for any post that isn’t a draft.
userId (the SMBcrm user creating the post, found with Users) is
required for posts to OAuth-connected accounts unless the post is a draft.
Each media item needs a url (a public HTTPS URL) and a type, which is its MIME type:
image/jpeg, image/jpg, image/png, image/gif, video/mp4, video/mov, or
video/webm.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/posts \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "accountIds": ["<social_account_id>"], "type": "post", "userId": "<user_id>", "status": "scheduled", "summary": "Fall is here. 10% off roof inspections this week.", "media": [{ "url": "https://cdn.example.com/fall-promo.jpg", "type": "image/jpeg" }], "scheduleDate": "2026-10-20T14:00:00.000Z" }'{ "success": true, "statusCode": 201, "message": "Created Post", "results": { "post": { "_id": "<post_id>", "locationId": "<location_id>", "source": "composer", "accountIds": ["<social_account_id>"], "type": "post", "summary": "Fall is here. 10% off roof inspections this week.", "media": [{ "url": "https://cdn.example.com/fall-promo.jpg", "type": "image/jpeg" }], "status": "scheduled", "displayDate": "2026-10-20T14:00:00.000Z", "createdBy": "<user_id>" } }}status controls whether and when the post publishes:
status |
Effect |
|---|---|
draft |
Saves the post without publishing it. Most validation is skipped, including accountIds and media requirements. |
scheduled |
Publishes at scheduleDate. scheduleDate is required. |
in_review |
Holds the post for approval. scheduleDate and postApprovalDetails.approver are required. |
The other values (published, in_progress, pending, failed, notification_sent, and
deleted) describe a post’s progress and show up on posts you read back.
Other body fields:
| Field | Description |
|---|---|
summary |
Caption text. Limits vary by platform. |
scheduleDate |
ISO 8601 date-time. Required when status is scheduled or in_review. |
tags |
Array of tag _id values from List tags. |
categoryId |
The _id of a category from List categories. |
applyWatermark |
true to apply the watermark template bound to each account when the post publishes. Applies to images only. See Watermarks. |
followUpComment |
A comment posted right after the post publishes. Supported on Facebook, Instagram, LinkedIn, Community, Threads, Bluesky, YouTube, and TikTok. Not supported on Google Business Profile or Pinterest. |
postApprovalDetails |
Approval settings: approver (a user ID), requesterNote, approverNote, and approvalStatus (pending, approved, rejected, or not_required). |
ogTagsDetails |
Link preview settings: metaLink, metaImage, ogTitle, and ogDescription. |
createdBy |
The 24-character hex ID of the user creating the post. |
Each media item also accepts caption, thumbnail (a cover image for the first video in
the post), and altText. Alt text is used on Instagram, Threads, Pinterest, Bluesky, and
LinkedIn image posts.
Platform-specific options
Section titled “Platform-specific options”Platform-specific detail objects carry per-platform options for whichever accounts in
accountIds belong to that platform:
| Object | Key fields |
|---|---|
facebookPostDetails |
type (post, story, or reel) and textFormatPresetId, a background preset for text-only feed posts. |
instagramPostDetails |
type, collaborators (a map of account ID to an array of Instagram usernames), publishViaPushNotification, publisherNote, and showOnFeed (reels only). |
youtubePostDetails |
type (video or short, required), title, and privacyLevel (private, public, or unlisted). |
tiktokPostDetails |
privacyLevel (PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, or SELF_ONLY), enableComment, enableDuet, enableStitch, videoDisclosure, promoteYourBrand, and promoteOtherBrand. |
linkedinPostDetails |
postAsPdf, pdfTitle, and poll, which takes a question, 2 to 4 options, and settings.duration (ONE_DAY, THREE_DAYS, SEVEN_DAYS, or FOURTEEN_DAYS). |
pinterestPostDetails |
title, link, and pinterestBoards. |
gmbPostDetails |
gmbEventType (STANDARD, EVENT, or OFFER, required) plus title, offerTitle, startDate, endDate, termsConditions, couponCode, redeemOnlineUrl, url, and actionType. |
communityPostDetails |
title, postAsUser, and notifyAllGroupMembers. See Community posts. |
Pinterest posts choose boards with pinterestPostDetails.pinterestBoards, an array of
{ "accountId": "<social_account_id>", "boards": ["<board_id>"] } entries with up to 25
boards per account. Each board becomes its own child post. The older boardIds field is
deprecated and past its announced removal date, so use pinterestBoards.
Community posts
Section titled “Community posts”A post to a Community account needs communityPostDetails with a title (up to 1,000
characters) and postAsUser. Community posts accept up to 4 media items and a caption of up
to 100,000 characters. Set notifyAllGroupMembers to true to notify every member of the
group, the same as an @everyone broadcast.
postAsUser names the member each Community account posts as. It is keyed by Community
account ID, which is the id of the account in the accounts list, not its oauthId:
{ "communityPostDetails": { "title": "Welcome to the community", "notifyAllGroupMembers": false, "postAsUser": { "<community_account_id>": { "id": "<contact_id>", "name": "Jane Cooper" } } }}id is required and is the contactId of the member. name and avatar are optional.
Every Community account in accountIds needs an entry in postAsUser, or the request is
rejected. The member must be active and hold a role in the group. This is checked again when
the post publishes, so a member who leaves the group after you schedule the post causes that
post to fail. For a private channel, list members per account, because only that channel’s
members can be posted as.
{accountId} is the id of a Community account from the accounts list. Only members who can
publish are returned. Pass a returned contactId as postAsUser.<community_account_id>.id
when you create a post.
| Query param | Type | Required | Description |
|---|---|---|---|
searchText |
string | No | Filter members by name. Use 2 or more characters; a shorter value returns no members. |
pageNo |
number | No | Page number. Default 1. |
limit |
number | No | Members per page. Maximum 100, default 10. |
curl "https://services.smbcrm.com/social-media-posting/<location_id>/community/accounts/<community_account_id>/users?searchText=jane&limit=10" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Community Members", "results": { "users": [ { "contactId": "<contact_id>", "fullName": "Jane Cooper", "avatar": "https://cdn.example.com/jane.png", "role": "admin", "status": "Active" } ], "meta": { "currentPage": 1, "nextPage": null, "prevPage": null, "total": 1 } }}meta.total is the number of members on this page, not across all pages. Keep paging until
meta.nextPage is null.
{groupId} is the meta.groupId of a Community account in the accounts list. {contactId}
is the contactId of a member. isActive is true only when the member is active and holds
a role in the group, the same condition checked at publish time. Use this call to confirm a
stored postAsUser is still valid before you schedule a post.
curl https://services.smbcrm.com/social-media-posting/<location_id>/community/groups/<group_id>/users/<contact_id>/validate \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Validated Community Member", "results": { "isActive": true, "userDetails": { "contactId": "<contact_id>", "fullName": "Jane Cooper", "role": "admin", "status": "Active" } }}Update & delete
Section titled “Update & delete”{postId} is the post’s _id. Send the full post payload. The body uses the same schema as
creating a post: type is required, and accountIds (for posts that aren’t drafts) and
userId (for posts to OAuth-connected accounts) are conditionally required.
curl -X PUT https://services.smbcrm.com/social-media-posting/<location_id>/posts/<post_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "accountIds": ["<social_account_id>"], "type": "post", "userId": "<user_id>", "status": "scheduled", "summary": "Fall is here. 15% off roof inspections this week.", "scheduleDate": "2026-10-21T14:00:00.000Z" }'{ "success": true, "statusCode": 200, "message": "Updated Post" }curl -X DELETE https://services.smbcrm.com/social-media-posting/<location_id>/posts/<post_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Deleted Post", "results": { "postId": "<platform_post_id>" }}Send postIds, an array of post IDs. Each is either a post’s 24-character _id or a
40-character native post ID. A request with more than 50 IDs returns 400, and a request
where none of the IDs match a post returns 404. The posts are deleted from SMBcrm only, so
check the IDs before you send the request.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/posts/bulk-delete \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "postIds": ["<post_id>", "<post_id>"] }'{ "success": true, "statusCode": 201, "message": "Posts Deleted Successfully", "results": { "message": "Posts deleted successfully", "deletedCount": 2 }}Categories & tags
Section titled “Categories & tags”Categories and tags help organize posts inside Social Planner. Pass a category’s _id as
categoryId, and tag _id values in tags, when you create or update a post.
Add searchText, limit, and skip to search and page through the results.
curl https://services.smbcrm.com/social-media-posting/<location_id>/categories \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Categories by Location ID", "results": { "count": 2, "categories": [ { "_id": "<category_id>", "name": "Promotions", "primaryColor": "#32a852", "secondaryColor": "#FFFFFF", "locationId": "<location_id>", "deleted": false }, { "_id": "<category_id>", "name": "Customer Stories", "primaryColor": "#004EEB", "secondaryColor": "#EFF4FF", "locationId": "<location_id>", "deleted": false } ] }}curl https://services.smbcrm.com/social-media-posting/<location_id>/categories/<category_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Category", "results": { "category": { "_id": "<category_id>", "name": "Promotions", "primaryColor": "#32a852", "secondaryColor": "#FFFFFF", "locationId": "<location_id>", "deleted": false } }}Add searchText, limit, and skip to search and page through the results.
curl https://services.smbcrm.com/social-media-posting/<location_id>/tags \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "statusCode": 200, "message": "Fetched Tags by Location ID", "results": { "tags": [ { "_id": "<tag_id>", "tag": "seasonal", "locationId": "<location_id>", "deleted": false }, { "_id": "<tag_id>", "tag": "evergreen", "locationId": "<location_id>", "deleted": false } ], "count": 2 }}Send tagIds, an array of tag _id values. It’s required.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/tags/details \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "tagIds": ["<tag_id>", "<tag_id>"] }'{ "success": true, "statusCode": 200, "message": "Fetched Tags by Tag IDs", "results": { "tags": [ { "_id": "<tag_id>", "tag": "seasonal", "locationId": "<location_id>", "deleted": false }, { "_id": "<tag_id>", "tag": "evergreen", "locationId": "<location_id>", "deleted": false } ], "count": 2 }}Category queues
Section titled “Category queues”A category queue publishes the posts in a category on a weekly schedule of time slots. A
new queue starts in draft status, and you activate it with an update. Published posts in
the category are added to the queue automatically.
The queue endpoints sit under /social-media-posting/category/queues and don’t take
locationId in the path. Send it in the body, or in the query string for GET and DELETE
calls. Responses put the data in results and add a traceId for debugging. Each time slot
has a dayOfWeek (0 for Sunday through 6) and a time in HH:mm format.
locationId, categoryId, timeSlots, and userId are required. enableFuturePosts and
prioritizeNewContent default to false.
curl -X POST https://services.smbcrm.com/social-media-posting/category/queues \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "categoryId": "<category_id>", "userId": "<user_id>", "timeSlots": [ { "dayOfWeek": 1, "time": "09:00" }, { "dayOfWeek": 4, "time": "09:00" } ], "enableFuturePosts": true }'{ "success": true, "statusCode": 201, "results": { "message": "Queue created successfully", "queue": { "_id": "<queue_id>", "locationId": "<location_id>", "categoryId": "<category_id>", "timeSlots": [ { "_id": "<time_slot_id>", "dayOfWeek": 1, "time": "09:00" }, { "_id": "<time_slot_id>", "dayOfWeek": 4, "time": "09:00" } ], "enableFuturePosts": true, "prioritizeNewContent": false, "status": "draft", "startDate": "2026-10-08T15:04:00.000Z", "skipDateTime": [], "totalPosts": 0, "lastScheduledTime": null, "createdBy": "<user_id>", "createdAt": "2026-10-08T15:04:00.000Z", "updatedAt": "2026-10-08T15:04:00.000Z" } }, "traceId": "<trace_id>"}Send locationId (required), skip, and limit as numbers. Deleted queues are left out.
The response returns 201 with results.queues and results.meta.count. Each queue
includes its category.
Query parameters: locationId (required), skip, limit, and q to search. Each category
has a status of available (no queue), in_queue (an active or paused queue), or draft
(a queue in draft), and a publishedPostsCount.
locationId is a required query parameter. The response returns the queue with its category
metadata, and counts the queue’s posts that have errors.
locationId is required. Optional fields:
status:active,paused, ordeleted.timeSlots: the time slots the queue publishes in.skipDateTime: an ISO 8601 date-time to skip.enableFuturePosts: queue new posts created in this category automatically.prioritizeNewContent: whentrue, new items added withdirectToQueuego to the top of the queue.skipLegacyWatermark: skip legacy watermark cleanup when posts are rescheduled.
curl -X PUT https://services.smbcrm.com/social-media-posting/category/queues/<queue_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "status": "active" }'locationId, startDate, and endDate (both ISO 8601 date-times) are required. Add
categoryIds or accountIds to filter. Each entry in results.scheduledPosts has a
scheduledDateTime and the post.
locationId is required. Optional fields: skip, limit, errorFilter (true returns
only items with errors), and itemId (centers the page around that item and ignores
skip). Pass sessionId to read the draft items of an edit session instead of the live
items.
locationId is required. skip defaults to 0 and limit to 20. Each entry in
results.slots has a scheduledDateTime and isSkipped, and the response includes
total and the timezone used. Pass sessionId for the slots of an edit session. Call
this after changing items to refresh the slot data.
locationId is required. Describe the post in modifiedPostPayload, which takes the same
fields as creating a post. Other fields: variations (each
with content, mentions, and ogTags), primaryImage, and order (a number, "top",
or "bottom"; the default is the end of the queue).
On an active or paused queue, add the item inside an edit session by passing sessionId.
To add it without a session, set directToQueue to true. The item then goes to the top of
the queue if the queue’s prioritizeNewContent is true and to the bottom if not, and
order is ignored.
locationId is required. Optional fields: sessionId, modifiedPostPayload,
variations, primaryImage, and newOrder (a number, "top", or "bottom"). The
response includes updatedSlots and totalPostsChanged for the items a reorder affects.
locationId is a required query parameter. Add sessionId to stage the deletion in an edit
session.
Send locationId (required) and, optionally, sessionId. Any changes to the item are
discarded.
Requires an active edit session. locationId, sessionId, and order are required.
order is a number, typically between the source item and the next one.
{postId} is the ID of the scheduled post. locationId is a required query parameter.
Edit sessions
Section titled “Edit sessions”An edit session stages changes to a queue’s items so you can review them before they go live. The flow is:
- Call
edit/startto get asessionId. It creates a draft copy of the queue’s items. - Pass
sessionIdto the item endpoints above to stage changes. - Preview the result with
edit/calendar. - Call
edit/saveto apply the changes and close the session, oredit/discardto throw them away. The live queue is untouched until you save.
Send locationId (required). The response returns results.sessionId and results.itemCount,
the number of items staged for editing.
locationId, sessionId, startDate, and endDate are required. Add accountIds to
filter. The response returns results.scheduledPosts, results.total, and the timezone
used.
locationId and sessionId are required. A queue in draft status is activated when you
save. Set keepInDraft to true to leave it in draft. The response returns the
updatedSlots and totalPostsChanged.
locationId and sessionId are required. The live queue is not changed.
Watermarks
Section titled “Watermarks”A watermark template stores the image, position, size, opacity, and padding to stamp on a
post’s images, plus the connected accounts it applies to. Set applyWatermark to true
when you create a post, and each account’s template is applied when the post publishes.
Watermarks apply to images only, not videos.
Template responses return the template directly in results:
{ "_id": "<template_id>", "watermarkImageUrl": "https://cdn.example.com/logo.png", "position": "bottom-right", "scale": 0.5, "opacity": 0.7, "padding": true, "templateName": "Logo bottom right", "locationId": "<location_id>", "createdBy": "<user_id>", "accountIds": ["<social_account_id>"], "deleted": false, "createdAt": "2026-10-08T15:04:00.000Z", "updatedAt": "2026-10-08T15:04:00.000Z"}Body fields:
| Field | Type | Description |
|---|---|---|
watermarkImageUrl |
string | URL of the watermark image. PNG or JPG, at least 200 x 200 pixels, and no larger than 5 MB. |
position |
string | top-left, top-center, top-right, left-center, center, right-center, bottom-left, bottom-center, or bottom-right. |
scale |
number | Scale factor, from 0 to 1. |
opacity |
number | Opacity, from 0 to 1. |
padding |
boolean | Whether to add padding around the watermark. |
templateName |
string | Name of the template. |
accountIds |
array | Connected account IDs to bind the template to. At publish time, the template is looked up through this binding. |
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/watermarks \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "watermarkImageUrl": "https://cdn.example.com/logo.png", "position": "bottom-right", "scale": 0.5, "opacity": 0.7, "padding": true, "templateName": "Logo bottom right", "accountIds": ["<social_account_id>"] }'The response returns 201 with the template in results.
Add limit, skip, and name (to search by template name) as query parameters. The
response returns results.watermarks and results.meta, which has the template count and
usingLegacy. usingLegacy is true when legacy watermarks exist, meaning templates with
no accounts bound to them.
The response returns the template in results, including the accountIds it applies to.
Send any of the create fields. Sending accountIds replaces the current binding entirely,
so include every account the template should apply to. Set deleted to true to
soft-delete the template.
The template is marked as deleted and stops applying to posts. The accounts it was bound to are released and can be bound to other templates.
inputMediaUrl is required. Identify the template with templateId, or send accountId to
use the template bound to that connected account. One of the two is required. Optional
fields: mimeType, postId (associates the output with a post), watermarkId (a cache key
for a result generated earlier), and updatePost (whether to update or create the linked
post after watermarking).
Processing is asynchronous. The response returns a progressId and a status of pending,
processing, completed, or failed. outputMediaUrl is present when status is
completed. If the same template and image were processed before, the response returns
completed right away.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/watermarks/add-image-watermark \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "templateId": "<template_id>", "inputMediaUrl": "https://cdn.example.com/fall-promo.jpg", "mimeType": "image/jpeg" }'{ "success": true, "statusCode": 200, "message": "Created Watermark Image", "results": { "progressId": "<progress_id>", "status": "pending", "message": "Watermark preview processing initiated." }}Bulk scheduling by CSV
Section titled “Bulk scheduling by CSV”Bulk scheduling takes three calls: upload the CSV file, choose the accounts it publishes to, then finalize the import to schedule every post in the file.
Send the file as multipart/form-data in a field named file, not as JSON. Drop the
Content-Type: application/json header and let your HTTP client set the multipart boundary.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/csv \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -F "file=@/path/to/posts.csv"{ "success": true, "statusCode": 201, "message": "Uploaded CSV", "results": { "filePath": "<file_path>", "rowsCount": 6, "fileName": "posts.csv", "fileSize": 1024, "csvFileType": "basic" }}csvFileType is basic or advance. Keep filePath, rowsCount, fileName, and
csvFileType for the next call.
accountIds, filePath, rowsCount, fileName, and userId are required. rowsCount
must be between 1 and the number of posts in the CSV. approver (a user ID) and
csvFileType are optional.
curl -X POST https://services.smbcrm.com/social-media-posting/<location_id>/set-accounts \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "accountIds": ["<social_account_id>"], "filePath": "<file_path>", "rowsCount": 6, "fileName": "posts.csv", "userId": "<user_id>", "csvFileType": "basic" }'{ "success": true, "statusCode": 201, "message": "Accounts Set Successfully", "results": { "csvId": "<csv_id>" }}Send userId, which is required.
curl -X PATCH https://services.smbcrm.com/social-media-posting/<location_id>/csv/<csv_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "userId": "<user_id>" }'{ "success": true, "statusCode": 200, "message": "Updated Successfully" }userId is a required query parameter. Optional parameters: skip (default "0"), limit
(default "10"), includeUsers, and isFromTemplate (to filter imports from the template
library). Each entry in results.csvs has a status of pending, in_progress,
completed, failed, in_review, importing, or deleted, and a post count. The
response also returns the total count of imports.
Add skip and limit to page through the posts. The response returns the import in
results.csv, its posts, and a count of posts. Each CSV post has a status of
pending, accepted, rejected, or deleted, and an errorMessage when a row failed
validation.
The response returns the deleted import in results.csv.
{postId} is the CSV post’s ID. The response returns the postId and the updated import in
results.csv.
Comments
Section titled “Comments”Comment endpoints take locationId as a query parameter, not in the path, and platform as a
path segment. platform is one of facebook, instagram, linkedin, community,
tiktok, bluesky, youtube, or threads. IDs for posts and comments are the 24-character
_id values, not the platform’s native IDs.
parentId, isParentThread, and content are required. For a top-level comment, set
isParentThread to true and parentId to the post’s _id. For a reply, set
isParentThread to false and parentId to the comment’s _id.
Maximum content length: Facebook 8,000, Instagram 2,200, LinkedIn 3,000, Community 8,000,
TikTok 150, Bluesky 300, YouTube 10,000, Threads 500.
| Optional field | Description |
|---|---|
attachments |
One image, as [{ "url": "...", "type": "image" }]. Facebook only. Other platforms accept the field and ignore the attachment. |
mentions |
Array of name, id, offset, and length entries, plus an optional slug for Community profile links. Works on Facebook, LinkedIn, and Community. content.substring(offset, offset + length) must equal name, or the request returns 400. |
notifyAllGroupMembers |
true to notify every member of the Community group. Community only. |
pollOptions, textAttachment, threadLocationId |
Threads replies only: poll options (2 to 4), a text attachment, and a location tag. |
curl -X POST "https://services.smbcrm.com/social-media-posting/comments/facebook?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "parentId": "<post_id>", "isParentThread": true, "content": "Thanks for stopping by!" }'{ "success": true, "statusCode": 201, "message": "Created Comment", "results": { "_id": "<comment_id>", "platform": "facebook", "platformCommentId": "<platform_comment_id>", "postId": "<post_id>", "originId": "<origin_id>", "isParentThread": true, "content": "Thanks for stopping by!", "likeCount": 0, "replyCount": 0, "isRead": false, "createdAt": "2026-10-08T15:04:00.000Z" }}originIds, an array of origin IDs of the connected accounts to include, is required. The
other fields are optional. Unlike post search, skip and limit here are numbers.
| Body field | Type | Description |
|---|---|---|
parentId |
string | A post _id to list comments on that post, or a comment _id to list its replies. Omit it to list the top-level comments for the location, filtered by originIds. |
fromDate, toDate |
string | A published-date window as ISO 8601 date-times. Send both or neither, and fromDate can’t be after toDate. |
sortBy |
string | top or latest. Default latest. |
search |
string | Keyword search. |
skip |
number | Comments to skip. Default 0. |
limit |
number | Comments to return, from 1 to 100. Default 10. |
curl -X POST "https://services.smbcrm.com/social-media-posting/comments/facebook/list?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "originIds": ["<origin_id>"], "parentId": "<post_id>", "sortBy": "latest", "limit": 10 }'{ "success": true, "statusCode": 201, "message": "Fetched Comments", "results": { "comments": [ { "_id": "<comment_id>", "platform": "facebook", "postId": "<post_id>", "originId": "<origin_id>", "isParentThread": true, "content": "Nice post!", "author": { "id": "<platform_author_id>", "name": "John Doe" }, "likeCount": 0, "replyCount": 0, "isRead": false, "publishedAt": "2026-10-08T15:04:00.000Z" } ], "meta": { "total": 1, "totalUnread": 1, "skip": 0, "limit": 10, "hasMore": false } }}{commentId} is the _id from the list call. It works for top-level comments and replies.
Supported on facebook, linkedin, community, tiktok, and bluesky. Instagram isn’t
supported and returns 400, as does liking a comment that is already liked. The response
returns 201 with the message Created Like.
Same platforms and {commentId} as liking a comment. Unliking a comment that isn’t liked
returns 400. The response returns 200 with the message Deleted Like.
Statistics
Section titled “Statistics”locationId is a required query parameter here, not part of the path. Every body field is
optional:
| Body field | Type | Description |
|---|---|---|
profileIds |
array | Connected account IDs to include. Up to 100. |
platforms |
array | Limit the results to facebook, instagram, linkedin, google, pinterest, youtube, or tiktok. Omit it to include all platforms. |
currentRange |
object | startDate and endDate of the period, as ISO 8601 date-times. |
prevRange |
object | startDate and endDate of the period to compare against. Without it, no comparison is made. |
Omit both ranges to get the last 7 days, not counting today, compared with the 7 days before.
curl -X POST "https://services.smbcrm.com/social-media-posting/statistics?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "profileIds": ["<social_account_id>"], "platforms": ["facebook", "instagram"], "currentRange": { "startDate": "2026-10-01T00:00:00.000Z", "endDate": "2026-10-07T23:59:59.999Z" }, "prevRange": { "startDate": "2026-09-24T00:00:00.000Z", "endDate": "2026-09-30T23:59:59.999Z" } }'The response has results, a message, and a traceId. This example is trimmed:
{ "results": { "dayRange": ["Thursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"], "grouping": "daily", "totals": { "posts": 12, "likes": 46, "followers": 12000, "impressions": 944, "comments": 11 } }, "message": "Analytics Built Successfully", "traceId": "<trace_id>"}results also holds postPerformance, breakdowns, platformTotals, and demographics.
demographics is filled only for Instagram accounts.
grouping depends on the length of the range: daily up to 10 days, bidaily up to 20,
weekly up to 60, and monthly or yearly beyond that. dayRange holds one label per
bucket.
