OmniSocialsOmniSocials
DocumentationAPI reference
Help centerSupport
  • Documentation
  • API reference
Developers
  • Quickstart
  • API reference
  • SDKs
  • MCP server
  • CLI
For AI agents
  • Docs for AI agents
  • llms.txt
  • OpenAPI spec
  • Postman collection
Product
  • OmniSocials
  • Pricing
  • Changelog
  • Help center
Company
  • Support
  • Privacy policy
  • Terms of service

© 2026 OmniSocials

OmniSocials on GitHubOmniSocials on XOmniSocials on LinkedIn
Overview
Get started
SDKs
CLI
MCPDocs for AI agentsWebhooks
Guides
    PostsStoriesReelsCross-postingMediaAnalyticsPost tagsSocial inboxAutomations
Platforms
Integrations
Migrate
Plans and billing
GlossaryChangelogAPI reference
Guides

Post tags API: label posts and compare what works

Tags are your own labels on your posts: the hook a video opens with ("Question hook"), its format ("Talking head"), the campaign it belongs to ("Black Friday"). They answer one question: which kind of post works best for you.

Tags stay in OmniSocials. They are never sent to a platform and never change a caption, so you can tag a post that is already live.

EndpointWhat it doesScope
GET /tagsList the workspace's tagsposts:read
POST /tagsCreate a tagposts:write
PATCH /tags/:idRename or recolour a tagposts:write
DELETE /tags/:idDelete a tag; it comes off every postposts:write
POST /tags/applyAdd, remove or replace the tags of up to 500 postsposts:write
GET /analytics/tagsCompare each tag with your normalanalytics:read

The full request and response schemas are in the API reference under Tags. Posts also take tags on POST /posts/create and PATCH /posts/:id, and GET /posts and GET /analytics/overview take a tag filter. Tags belong to a workspace. The dashboard uses the same tags, and its Analytics page shows the same comparison under Tags.

The tag object

JSON
{ "id": 12, "name": "Question hook", "color": "blue", "created_at": "2026-11-02T09:14:00.000Z" }
FieldMeaning
idThe tag id. Use it, or the name, wherever a tag is asked for.
nameUp to 40 characters, unique in the workspace (case-insensitive). Runs of spaces become one space.
colorblue, green, orange, red, purple, pink, teal, yellow, brown or gray. Only the app uses it. Leave it out on create to get the next colour of the palette.
created_atWhen the tag was made.

The tags of a post are tag objects without created_at.

Create, rename and delete tags

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/tags \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Question hook" }'

The response is 201 with the new tag in data. A name the workspace already has answers 409 tag_exists, with that tag in error.tag, so you can use its id instead.

You rarely need POST /tags: wherever you give a tag by name (tags on a post, add or set on POST /tags/apply), a name that does not exist yet becomes a new tag.

PATCH /tags/:id renames or recolours a tag. A key you leave out keeps its value, and the posts keep the tag:

TerminalTerminal
curl -X PATCH https://api.omnisocials.com/v1/tags/12 \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Question opener" }'

DELETE /tags/:id deletes the tag and takes it off every post of the workspace. The posts stay. The response names the tag and how many posts carried it:

JSON
{ "data": { "tag": { "id": 12, "name": "Question opener", "color": "blue", "created_at": "2026-11-02T09:14:00.000Z" }, "posts_untagged": 14 } }

Tag a post

Pass tags when you create a post:

JSON
POST /posts/create { "content": { "default": "Would you post this at 6am?" }, "accounts": ["tiktok", "instagram"], "type": "reel", "media_ids": ["508"], "schedule_at": "2026-11-10T09:00:00Z", "tags": ["Question hook", "Talking head"] }

tags takes tag names or ids, mixed:

  • A name matches case-insensitively. A name that does not exist yet becomes a new tag of the workspace.
  • A number, or a string of digits such as "12", is read as a tag id. An unknown id answers 404 tag_not_found.
  • At most 20 tags per post. A tag named twice counts once.

The post comes back with tags as a list of tag objects. GET /posts and GET /posts/:id return them too.

PATCH /posts/:id with tags replaces the post's tags; null or [] removes them all. A PATCH that sends only tags works on every post, published and failed posts included. A PATCH with other fields still follows the normal rule: a published or failed post cannot change.

TerminalTerminal
curl -X PATCH https://api.omnisocials.com/v1/posts/1024 \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tags": ["Question hook"] }'

Tag many posts at once

POST /tags/apply changes the tags of up to 500 posts in one call, published posts included. Use it to tag your older posts, so the comparison has enough posts to work with.

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/tags/apply \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "post_ids": ["1024", "1025", "1031"], "add": ["Question hook"] }'
FieldMeaning
post_idsRequired. 1 to 500 OmniSocials post ids (the id from GET /posts).
addTags to add, by name or id. A new name becomes a tag. The post's other tags stay.
removeTags to take off, by name or id. An unknown name answers 404 tag_not_found.
setReplaces each post's tags with exactly these. [] removes them all.

Give at least one of add, remove and set. When you give more than one, set goes first, then add, then remove.

JSON
{ "data": { "updated": 1, "posts": [ { "id": "1024", "tags": [{ "id": 12, "name": "Question hook", "color": "blue" }] }, { "id": "1025", "tags": [{ "id": 12, "name": "Question hook", "color": "blue" }, { "id": 15, "name": "Talking head", "color": "green" }] } ], "not_found": ["1031"] } }
  • posts lists every post that was found, with its tags now.
  • updated counts the posts whose tags changed. Here post 1024 already had the tag, so only 1025 counts.
  • not_found lists the ids that are not posts of this workspace. The other posts are still tagged.

The call is all or nothing for the posts it finds: when one post would end up with more than 20 tags, it answers 400 too_many_tags with that post in error.post_id, and no post changes.

Filter by tag

GET /posts?tag= returns only the posts with that tag. Comma separate several tags to get the posts that have any of them. Names and ids both work:

TerminalTerminal
curl "https://api.omnisocials.com/v1/posts?tag=Question%20hook,Talking%20head&status=published" \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY"

GET /analytics/overview?tag= works the same way: the post numbers (total_posts, engagement, impressions, the platform breakdown) then cover only the posts with the tag. Account numbers such as followers stay those of the whole account.

A filter never makes a tag: an unknown name or id answers 404 tag_not_found. For a tag whose name holds a comma, filter by its id.

Compare tags

GET /analytics/tags shows how the posts of each tag did against your normal, for the posts published in a period.

TerminalTerminal
curl "https://api.omnisocials.com/v1/analytics/tags?start_date=2026-08-01&end_date=2026-10-31" \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY"
ParameterMeaning
start_dateYYYY-MM-DD, YYYY-MM or ISO 8601. Default: 90 days before end_date.
end_dateSame formats, inclusive. Default: today.
platformOnly this channel's numbers, for example tiktok. It must be a connected channel. Default: every connected channel.
min_postsHow many measured posts a tag needs before enough_posts is true. Default 5, from 1 to 50.
JSON
{ "data": { "period": { "start_date": "2026-08-01T00:00:00.000Z", "end_date": "2026-10-31T23:59:59.999Z" }, "platform": null, "min_posts": 5, "tags": [ { "tag": { "id": 12, "name": "Question hook", "color": "blue" }, "posts": 14, "measured": 13, "median_impressions": 4210, "engagement_rate": 5.4, "vs_normal": 0.38, "best_channel": "tiktok", "enough_posts": true }, { "tag": { "id": 15, "name": "Talking head", "color": "green" }, "posts": 9, "measured": 9, "median_impressions": 2890, "engagement_rate": 4.1, "vs_normal": -0.12, "best_channel": "instagram", "enough_posts": true }, { "tag": { "id": 18, "name": "Black Friday", "color": "orange" }, "posts": 3, "measured": 2, "median_impressions": 6100, "engagement_rate": 6.0, "vs_normal": 0.9, "best_channel": null, "enough_posts": false } ], "untagged": { "posts": 41, "measured": 38, "median_impressions": 3050, "engagement_rate": 4.6, "vs_normal": 0.02, "best_channel": "tiktok", "enough_posts": true }, "normals": { "tiktok": 3400, "instagram": 2900, "linkedin": 1100 } } }
FieldMeaning
postsPosts with the tag in the period
measuredThose posts that have numbers yet
median_impressionsThe median of the measured posts' impressions, each post summed over its channels
engagement_rateEngagement divided by impressions over all measured posts, in percent (5.4 = 5.4%)
vs_normalHow far above or below your normal the tag's posts are: 0.38 = 38% above, -0.12 = 12% below, 0 = normal. null without numbers
best_channelThe channel where the tag's posts beat your normal by the most. Only channels with at least 3 of the tag's posts count; null when none has that many
enough_postsfalse below min_posts measured posts. Read vs_normal then as a hint, not a result

tags lists the tags with enough posts first, best vs_normal first, then the tags with too few posts. untagged has the same numbers for the posts without a tag. normals is your normal per channel.

How vs_normal is computed

  1. Your normal per channel. For each channel, the median impressions of every measured post on that channel in the period. These are the normals.
  2. An index per post. Each post's impressions on a channel are divided by that channel's normal, and the post's index is the mean over its channels. A TikTok post is compared with your TikTok posts, a LinkedIn post with your LinkedIn posts.
  3. A value per tag. vs_normal is the median index of the tag's posts, minus 1.

Raw views would let a tag win only because its posts went to the channel with the most views. The median keeps one viral post from carrying a tag. Impressions here are the same number as in the rest of the analytics: views where the platform gives them, else impressions, else reach (see How engagement is calculated).

Which posts count:

  • Published posts (published or warning) whose publish time is in the period, on a connected channel (or on platform when you pass it).
  • A post with two tags counts in both rows.
  • Posts made outside OmniSocials do not count.

A post that published minutes ago has no numbers yet and is not measured. See How metrics are collected.

Trial reels

Posts carry trial_reel: { "strategy": "MANUAL" } or { "strategy": "SS_PERFORMANCE" } when the post went to Instagram as a Trial Reel (instagram.is_trial_reel), and null on every other post. It is on GET /posts, GET /posts/:id and each entry of GET /analytics/posts, so you can compare trial reels with your other reels.

JSON
{ "post_id": "1024", "trial_reel": { "strategy": "MANUAL" }, "tags": [{ "id": 12, "name": "Question hook", "color": "blue" }], "platforms": { "instagram": { "metrics": { "views": 5120, "likes": 210 } } } }

Instagram's API does not say whether a trial reel was shared with your followers later. trial_reel is the setting the post went out with, not what happened after. To see trial reels in the tag comparison, tag them, for example "Trial reel".

Limits

LimitValue
Tags per workspace200
Tags per post20
Posts per POST /tags/apply500
Tag name40 characters

Errors

Tag errors follow the shared error shape. Some add a field on error, named in the table.

StatusCodeMeaning
400invalid_tag_nameThe name is empty or longer than 40 characters
400invalid_tag_colorcolor is not one of the 10 colours
400invalid_tagstags, add, remove or set is not a list of names or ids
400invalid_tag_idsPOST /tags/apply without add, remove or set
400invalid_post_idsPOST /tags/apply without a post id
400too_many_postsMore than 500 post ids
400too_many_tagsMore than 20 tags on one post; on POST /tags/apply the post is in error.post_id
400invalid_date, invalid_date_range, invalid_platformGET /analytics/tags: a date it cannot read, a start after the end, or a channel that is not connected
404tag_not_foundAn unknown tag id (in error.tag_ids), or an unknown name in remove or a tag filter (in error.name)
409tag_existsAnother tag has that name; it is in error.tag
409tag_limit_reachedThe workspace has 200 tags. Delete one you no longer use first

Using it from an AI assistant

The MCP server has list_tags, create_tag, update_tag, delete_tag, tag_posts (the bulk call) and get_tag_analytics. create_post, create_and_publish_post and update_post take tags, and list_posts and get_analytics_overview take tag. get_post and list_posts show each post's tags and mark trial reels.

An assistant can tag your older posts for you: it reads the captions with list_posts, tags them with tag_posts, and then reads the result with get_tag_analytics. It reuses the names from list_tags, so no near-duplicate tags appear.

The CLI has tags:list, tags:create, tags:update, tags:delete and tags:apply, and analytics:tags for the comparison.

Last modified on November 2, 2026
AnalyticsSocial inbox
On this page
  • The tag object
  • Create, rename and delete tags
  • Tag a post
  • Tag many posts at once
  • Filter by tag
  • Compare tags
    • How vs_normal is computed
  • Trial reels
  • Limits
  • Errors
  • Using it from an AI assistant
JSON
JSON
JSON
JSON
JSON
JSON