Skip to content

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.
GET/social-media-posting/{locationId}/accounts

List the social accounts connected to your SMBcrm account/location, along with your account groups.

scope socialplanner/account.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/social-media-posting/<location_id>/accounts \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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.

DELETE/social-media-posting/{locationId}/accounts/{accountId}

Disconnect a social account from your SMBcrm account/location.

scope socialplanner/account.writeauth Location token or PIT

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.

Terminal window
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"
200 OK
{
"success": true,
"statusCode": 200,
"message": "Deleted Account",
"results": {
"locationId": "<location_id>",
"id": "<social_account_id>"
}
}

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.

GET/social-media-posting/oauth/{platform}/start

Step 1. Send the user to the platform's sign-in screen.

scope socialplanner/oauth.readonlyauth Location token or PIT

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
}
});
GET/social-media-posting/oauth/{locationId}/{platform}/accounts/{accountId}

Step 2. List the pages, channels, or locations the user can connect.

scope socialplanner/oauth.readonlyauth Location token or PIT

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

Terminal window
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"
POST/social-media-posting/oauth/{locationId}/{platform}/accounts/{accountId}

Step 3. Connect the page, channel, or location the user picked.

scope socialplanner/oauth.writeauth Location token or PIT

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).
Terminal window
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"
}'
201 Created
{
"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
}
}
POST/social-media-posting/{locationId}/posts/list

Search scheduled and published posts with filters and paging. This is the recommended way to list posts.

scope socialplanner/post.readonlyauth Location token or PIT

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.

Terminal window
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"
}'
201 Created
{
"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.
GET/social-media-posting/{locationId}/posts/{postId}

Fetch a single post by ID.

scope socialplanner/post.readonlyauth Location token or PIT

{postId} is the _id of the post. The endpoint also accepts the platform’s own 24-character hexadecimal post ID.

Terminal window
curl https://services.smbcrm.com/social-media-posting/<location_id>/posts/<post_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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"
}
}
}

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.

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.

POST/social-media-posting/{locationId}/posts

Create and schedule a post to one or more connected accounts.

scope socialplanner/post.writeauth Location token or PIT

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.

Terminal window
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"
}'
201 Created
{
"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 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.

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.

GET/social-media-posting/{locationId}/community/accounts/{accountId}/users

List the members you can publish as for one Community account.

scope socialplanner/oauth.readonlyauth Location token or PIT

{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.
Terminal window
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"
200 OK
{
"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.

GET/social-media-posting/{locationId}/community/groups/{groupId}/users/{contactId}/validate

Check that a member can still publish in a Community group.

scope socialplanner/account.readonlyauth Location token or PIT

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

Terminal window
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"
200 OK
{
"success": true,
"statusCode": 200,
"message": "Validated Community Member",
"results": {
"isActive": true,
"userDetails": {
"contactId": "<contact_id>",
"fullName": "Jane Cooper",
"role": "admin",
"status": "Active"
}
}
}
PUT/social-media-posting/{locationId}/posts/{postId}

Update fields on an existing post.

scope socialplanner/post.writeauth Location token or PIT

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

Terminal window
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"
}'
200 OK
{ "success": true, "statusCode": 200, "message": "Updated Post" }
DELETE/social-media-posting/{locationId}/posts/{postId}

Delete a post.

scope socialplanner/post.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/social-media-posting/<location_id>/posts/<post_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"success": true,
"statusCode": 200,
"message": "Deleted Post",
"results": { "postId": "<platform_post_id>" }
}
POST/social-media-posting/{locationId}/posts/bulk-delete

Delete up to 50 posts in one request.

scope socialplanner/post.writeauth Location token or PIT

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.

Terminal window
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>"] }'
201 Created
{
"success": true,
"statusCode": 201,
"message": "Posts Deleted Successfully",
"results": {
"message": "Posts deleted successfully",
"deletedCount": 2
}
}

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.

GET/social-media-posting/{locationId}/categories

List the categories available for organizing posts.

scope socialplanner/category.readonlyauth Location token or PIT

Add searchText, limit, and skip to search and page through the results.

Terminal window
curl https://services.smbcrm.com/social-media-posting/<location_id>/categories \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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
}
]
}
}
GET/social-media-posting/{locationId}/categories/{categoryId}

Fetch a single category by ID.

scope socialplanner/category.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/social-media-posting/<location_id>/categories/<category_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"success": true,
"statusCode": 200,
"message": "Fetched Category",
"results": {
"category": {
"_id": "<category_id>",
"name": "Promotions",
"primaryColor": "#32a852",
"secondaryColor": "#FFFFFF",
"locationId": "<location_id>",
"deleted": false
}
}
}
GET/social-media-posting/{locationId}/tags

List the tags available for organizing posts.

scope socialplanner/tag.readonlyauth Location token or PIT

Add searchText, limit, and skip to search and page through the results.

Terminal window
curl https://services.smbcrm.com/social-media-posting/<location_id>/tags \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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
}
}
POST/social-media-posting/{locationId}/tags/details

Fetch specific tags by their IDs.

scope socialplanner/tag.readonlyauth Location token or PIT

Send tagIds, an array of tag _id values. It’s required.

Terminal window
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>"] }'
200 OK
{
"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
}
}

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.

POST/social-media-posting/category/queues

Create a queue, in draft status, for a category.

scope socialplanner/category.writeauth Location token or PIT

locationId, categoryId, timeSlots, and userId are required. enableFuturePosts and prioritizeNewContent default to false.

Terminal window
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
}'
201 Created
{
"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>"
}
POST/social-media-posting/category/queues/list

List the category queues for a location.

scope socialplanner/category.readonlyauth Location token or PIT

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.

GET/social-media-posting/category/queues/available-categories

List categories with their queue status.

scope socialplanner/category.readonlyauth Location token or PIT

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.

GET/social-media-posting/category/queues/{queueId}

Fetch a single queue by ID.

scope socialplanner/category.readonlyauth Location token or PIT

locationId is a required query parameter. The response returns the queue with its category metadata, and counts the queue’s posts that have errors.

PUT/social-media-posting/category/queues/{queueId}

Activate, pause, or delete a queue, or change its time slots and settings.

scope socialplanner/category.writeauth Location token or PIT

locationId is required. Optional fields:

  • status: active, paused, or deleted.
  • 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: when true, new items added with directToQueue go to the top of the queue.
  • skipLegacyWatermark: skip legacy watermark cleanup when posts are rescheduled.
Terminal window
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" }'
POST/social-media-posting/category/queues/list/calendar

List the posts scheduled from active queues within a date range.

scope socialplanner/category.readonlyauth Location token or PIT

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.

POST/social-media-posting/category/queues/{queueId}/items

List the items in a queue.

scope socialplanner/category.readonlyauth Location token or PIT

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.

POST/social-media-posting/category/queues/{queueId}/slots

Get the scheduled time for each item in a queue.

scope socialplanner/category.readonlyauth Location token or PIT

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.

POST/social-media-posting/category/queues/{queueId}/create/item

Add a post item to a queue.

scope socialplanner/category.writeauth Location token or PIT

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.

PUT/social-media-posting/category/queues/{queueId}/items/{itemId}

Update the content, variations, or position of a queue item.

scope socialplanner/category.writeauth Location token or PIT

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.

DELETE/social-media-posting/category/queues/{queueId}/items/{itemId}

Delete an item from a queue.

scope socialplanner/category.writeauth Location token or PIT

locationId is a required query parameter. Add sessionId to stage the deletion in an edit session.

PUT/social-media-posting/category/queues/{queueId}/items/{itemId}/reset

Reset a queue item to its original state.

scope socialplanner/category.writeauth Location token or PIT

Send locationId (required) and, optionally, sessionId. Any changes to the item are discarded.

POST/social-media-posting/category/queues/{queueId}/items/{itemId}/clone

Duplicate a queue item at a position you choose.

scope socialplanner/category.writeauth Location token or PIT

Requires an active edit session. locationId, sessionId, and order are required. order is a number, typically between the source item and the next one.

DELETE/social-media-posting/category/queues/{postId}/active-post

Delete the post that is currently scheduled from a queue and schedule the next one.

scope socialplanner/category.writeauth Location token or PIT

{postId} is the ID of the scheduled post. locationId is a required query parameter.

An edit session stages changes to a queue’s items so you can review them before they go live. The flow is:

  1. Call edit/start to get a sessionId. It creates a draft copy of the queue’s items.
  2. Pass sessionId to the item endpoints above to stage changes.
  3. Preview the result with edit/calendar.
  4. Call edit/save to apply the changes and close the session, or edit/discard to throw them away. The live queue is untouched until you save.
POST/social-media-posting/category/queues/{queueId}/edit/start

Start an edit session.

scope socialplanner/category.writeauth Location token or PIT

Send locationId (required). The response returns results.sessionId and results.itemCount, the number of items staged for editing.

POST/social-media-posting/category/queues/{queueId}/edit/calendar

Preview how posts would be scheduled if you saved the session.

scope socialplanner/category.readonlyauth Location token or PIT

locationId, sessionId, startDate, and endDate are required. Add accountIds to filter. The response returns results.scheduledPosts, results.total, and the timezone used.

POST/social-media-posting/category/queues/{queueId}/edit/save

Apply the staged changes to the live queue and close the session.

scope socialplanner/category.writeauth Location token or PIT

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.

POST/social-media-posting/category/queues/{queueId}/edit/discard

Cancel the session and delete the staged changes.

scope socialplanner/category.writeauth Location token or PIT

locationId and sessionId are required. The live queue is not changed.

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"
}
POST/social-media-posting/{locationId}/watermarks

Create a reusable watermark template.

scope socialplanner/watermarks.writeauth Location token or PIT

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.
Terminal window
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.

GET/social-media-posting/{locationId}/watermarks

List watermark templates.

scope socialplanner/watermarks.readonlyauth Location token or PIT

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.

GET/social-media-posting/{locationId}/watermarks/{templateId}

Fetch a single watermark template.

scope socialplanner/watermarks.readonlyauth Location token or PIT

The response returns the template in results, including the accountIds it applies to.

PUT/social-media-posting/{locationId}/watermarks/{templateId}

Update a watermark template.

scope socialplanner/watermarks.writeauth Location token or PIT

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.

DELETE/social-media-posting/{locationId}/watermarks/{templateId}

Delete a watermark template.

scope socialplanner/watermarks.writeauth Location token or PIT

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.

POST/social-media-posting/{locationId}/watermarks/add-image-watermark

Apply a watermark to an image.

scope socialplanner/watermarks.writeauth Location token or PIT

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.

Terminal window
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"
}'
200 OK
{
"success": true,
"statusCode": 200,
"message": "Created Watermark Image",
"results": {
"progressId": "<progress_id>",
"status": "pending",
"message": "Watermark preview processing initiated."
}
}

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.

POST/social-media-posting/{locationId}/csv

Upload a CSV file of posts.

scope socialplanner/csv.writeauth Location token or PIT

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.

Terminal window
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"
201 Created
{
"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.

POST/social-media-posting/{locationId}/set-accounts

Choose the accounts an uploaded CSV publishes to.

scope socialplanner/csv.writeauth Location token or PIT

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.

Terminal window
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"
}'
201 Created
{
"success": true,
"statusCode": 201,
"message": "Accounts Set Successfully",
"results": { "csvId": "<csv_id>" }
}
PATCH/social-media-posting/{locationId}/csv/{csvId}

Finalize the import and schedule all its posts.

scope socialplanner/csv.writeauth Location token or PIT

Send userId, which is required.

Terminal window
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>" }'
200 OK
{ "success": true, "statusCode": 200, "message": "Updated Successfully" }
GET/social-media-posting/{locationId}/csv

List the CSV imports for a location with their status.

scope socialplanner/csv.readonlyauth Location token or PIT

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.

GET/social-media-posting/{locationId}/csv/{csvId}

Get one CSV import and its posts.

scope socialplanner/csv.readonlyauth Location token or PIT

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.

DELETE/social-media-posting/{locationId}/csv/{csvId}

Delete a CSV import and all its posts.

scope socialplanner/csv.writeauth Location token or PIT

The response returns the deleted import in results.csv.

DELETE/social-media-posting/{locationId}/csv/{csvId}/post/{postId}

Delete one post from a CSV import.

scope socialplanner/csv.writeauth Location token or PIT

{postId} is the CSV post’s ID. The response returns the postId and the updated import in results.csv.

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.

POST/social-media-posting/comments/{platform}

Create a top-level comment on a post, or a reply to a comment.

scope socialplanner/comments.writeauth Location token or PIT

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.
Terminal window
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!"
}'
201 Created
{
"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"
}
}
POST/social-media-posting/comments/{platform}/list

List the comments on a post or under a comment.

scope socialplanner/comments.readonlyauth Location token or PIT

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.
Terminal window
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
}'
201 Created
{
"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
}
}
}
POST/social-media-posting/comments/{platform}/{commentId}/like

Like a comment.

scope socialplanner/comments.writeauth Location token or PIT

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

DELETE/social-media-posting/comments/{platform}/{commentId}/like

Remove your like from a comment.

scope socialplanner/comments.writeauth Location token or PIT

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.

POST/social-media-posting/statistics

Get analytics for connected accounts over a date range, compared with a previous range.

scope socialplanner/statistics.readonlyauth Location token or PIT

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.

Terminal window
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:

200 OK
{
"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.

  • Locations. Connected accounts and posts belong to a single SMBcrm account/location.
  • Users. Look up the user IDs you pass as userId.
  • Blogs. Another content type you can create and publish through the API.