- Brand
- Unknown
- Category
- Pending
- Primary Subcategory
- Social Media Management & Scheduling
Integration details
Description
Antwork is a social media scheduler for ChatGPT: it drafts, schedules and publishes posts to your connected social accounts and reports how they perform. It connects LinkedIn, X, Facebook, Instagram, Threads, TikTok, YouTube, and Pinterest accounts to a workspace, then drafts posts that match the voice of the account's own recent posts, attaches images or video, schedules them for a chosen time or publishes them immediately, and reports back on engagement. Typical workflows are drafting and publishing a single post, planning and scheduling a week of content across accounts, attaching media from the chat or the workspace library, reviewing the content calendar, and checking which published posts performed best.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Category
- Pending
- Primary Subcategory
- Social Media Management & Scheduling
- Secondary Subcategories
- None listed
- Brand
- Unknown
- Access
- Account required
- First tracked
- 2026-10-21
- Tool count
- 37
- Geography
- US
The broad Category that contains the Primary Subcategory.
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for Antwork
Get updates when Antwork’s Discoverability Score or category rank changes.
Competing in ChatGPT Social Media Management & Scheduling
View Category37 tools agents can invoke
Put a file the user attached in ChatGPT into the workspace library. THE local-file path on ChatGPT, and the only one that works there. The signed-URL flow cannot: ChatGPT's tool runtime cannot reach or resolve `storage.googleapis.com`, so the PUT never completes no matter how the model holds it. `upload_media_inline` works but carries the bytes through the transcript, which is fine for a screenshot and hopeless for video. Antwork downloads the file server-side from `download_url`, so the bytes never enter the conversation and a 100 MB video costs the same context as a 10 KB one. Pass `attach_to_post_id` to land it on a draft in the same step. The file is added to the media the draft already has; nothing attached is removed. Size caps are the store's own: 10 MB for images, 100 MB for video and PDF. KNOWN LIMITATION — the ChatGPT mobile apps do not send a real file object here. They send a placeholder string with no URL, which is an OpenAI bug (openai/openai-apps-sdk-examples#185), not something this server can work around. On mobile, tell the user to add the file from the Media page in the antwork.io web app and then use `list_media` + `attach_media`. Args: file: The attachment, supplied by ChatGPT. Do not synthesize this — it only arrives when the user actually attached a file. attach_to_post_id: Draft to add the file to, if there is one. workspace_id: Workspace ID or name. Optional if the user has only one.
attach_chatgpt_file
Set the media (images, videos, or PDFs) on an existing post. Sets the media URLs on the post. Use URLs from list_media or any Firebase Storage URL from the workspace. This REPLACES any existing media on the post, so pass the full list you want the post to end up with — and pass an EMPTY list to remove all media from it. There is no separate detach tool; this is the only writer of a post's media. Required for publishing to visual platforms like Instagram, TikTok, and Pinterest which require at least one media file. PDFs are only rendered natively by LinkedIn (as document posts); other platforms will skip PDF attachments at publish time. Clearing the media on a post for a platform that requires it returns a `publishBlocker` saying so — the write still happens, because a draft is allowed to be incomplete. Args: post_id: The post ID to set media on. media_urls: List of media URLs (from list_media or Firebase Storage). Empty list clears the post's media. workspace_id: Workspace ID or name. Optional if user has only one workspace.
attach_media
Create a comment on a LinkedIn post (LinkedIn only). Two ways to target the post: - `post_id`: an Antwork post you published. Resolves the LinkedIn URN and the publishing account automatically. The post MUST already be published (a draft has no LinkedIn URN yet). This is the way to add a "first comment" — e.g. a link or CTA — under your own post right after publishing. - `post_urn` + `account_id`: comment on an explicit LinkedIn post/share/ ugcPost URN using a specific connected account (from list_social_accounts). Use this to reply on a known URN that isn't an Antwork post. For a nested reply to an existing comment, pass `parent_comment_urn`. Requires the account to have been connected with comment permission (w_member_social_feed for personal, w_organization_social_feed for org pages). If LinkedIn returns 403, the account connected before comment scope was granted — tell the user to reconnect it via its reauthUrl. Args: text: The comment text. post_id: Antwork post ID to comment on (resolves URN + account). post_urn: Explicit LinkedIn post/share/ugcPost URN (use with account_id). account_id: Social-account doc ID to comment as (required with post_urn). parent_comment_urn: Parent comment URN for a nested reply. workspace_id: Workspace ID or name. Optional if user has only one workspace.
comment_post
Draft the same idea across several accounts in one call. Each variant carries its OWN copy, written for that account. Creates DRAFTS only — follow with schedule_campaign or publish_campaign. Use this instead of calling create_post once per account when the user wants one idea on several accounts. It writes one independent post per account, all sharing a campaign so the user sees them as a set, and attaches shared media to every one of them in the same call. WRITE EACH VARIANT SEPARATELY. `variants` takes per-account copy on purpose: there is no "one body, many accounts" parameter, because the same text on LinkedIn and X is worse on both. Before drafting, call `get_post_context(account_ids=[...])` once for every target account and match each account's own recent posts. The response renders the posts table, not a post card — the set is the thing the user is looking at. To open or edit one of them, the user clicks its row; you can also call `get_post` for a single editable card. Media, character limits and account health work exactly as in create_post, per variant. A variant that fails validation is reported in `posts` with its reason and the others still get created. Args: variants: One entry per target account: `{"account_id": str, "text": str}` plus optional `hashtags`, `goal`, `user_tags`, `pinterest_board_id`, `platform_options` — the same fields create_post takes. Account IDs come from list_social_accounts. campaign_id: Reuse an existing campaign to add accounts to it. Omit to mint a new one. media_urls: Attached to EVERY post in the campaign. Use it for the one image or video the whole set shares. scheduled_for: ISO 8601. Pre-fills the date on every draft. Does NOT schedule anything — call schedule_campaign. workspace_id: Workspace ID or name. Optional if user has only one workspace.
create_campaign
Create ONE draft for ONE social account. Drafts only: schedule_post or publish_post sends it. - Saves the draft to the user's workspace and renders it as a post card, where the user can edit it, add media, and schedule or publish. - `account_id` is a social_account id from `workspaceAccounts` or list_social_accounts, never a platform name. With several accounts on a platform and no clear choice, ask the user first. - Voice comes from get_post_context(platform, account_id) for this account. Per account: a company page and a personal profile differ. - One idea on several accounts is create_campaign, not a loop of this tool. - Several versions for this account is ONE call: `text` is version A and `variants` holds up to 3 more, each a genuinely different angle. They render as one card with a version switcher. Looping paints one card per call. Platform rules the response enforces (details in the server instructions): - TikTok and YouTube need a video; Instagram and Pinterest need an image or video. Such a draft returns `mediaRequired`, and publish/schedule refuse it until attach_media runs, so ask for media in the same turn. - YouTube uses the first 100 characters as the title unless `platform_options.title` is set; `derivedTitle` says when that applies. - TikTok's audience is the account owner's choice, set on the card; it cannot be preselected, and publishing refuses with `tiktok_privacy_required` until it is. - Pinterest needs `pinterest_board_id` (from `boards` in get_post_context); without one the response carries a `pinterestBoard` message to follow. Args: text: Post copy. @mentions in text auto-link on X, Threads, TikTok and Facebook; LinkedIn rich mentions and Instagram photo tags go in `user_tags`. account_id: Social-account id the post publishes to. Required. hashtags: Without the # prefix. A list, or a comma/space-separated string. goal: e.g. "Educate", "Engage", "Promote", "Entertain". scheduled_for: ISO 8601. Pre-fills the date only; schedule_post schedules. campaign_id: Add this draft to an existing campaign. user_tags: [{username, platform, platformId (LinkedIn URN), type ("mention" | "photo_tag"), x, y (0-1, Instagram photo tags)}]. pinterest_board_id: Pinterest only. Omitted = the account's first board. platform_options: Only what the user asked for. Instagram: publishType ("timeline" | "story"), postToGrid. Pinterest: pinTitle (≤100). YouTube: privacyStatus ("public" | "unlisted" | "private"), title (≤100), categoryId (e.g. "22", "28"), madeForKids. TikTok settings are set on the card, not here. variants: Up to 3 alternative versions for the same account (see above). workspace_id: Workspace ID or name. Optional if user has only one workspace.
create_post
Create a new workspace owned by the authenticated user. Use this when the user wants a separate brand / client / project space — each workspace has its own connected social accounts, posts, and media library. Subsequent tool calls can target the new workspace by passing `workspace_id` (its returned ID or its name). Args: name: Workspace name (required, 1–50 characters).
create_workspace
Delete a media item from the workspace media library. This removes the Firestore record. The file in storage may still exist but will no longer appear in the media library. Args: media_id: The media item ID to delete. workspace_id: Workspace ID or name. Optional if user has only one workspace.
delete_media
Delete a post from the workspace. Drafts and scheduled posts are hard-deleted. Published posts are soft-deleted by default (the post is hidden from lists but metrics history is preserved); pass preserve_analytics=False to hard-delete a published post instead. Args: post_id: The post ID to delete. delete_from_platforms: If True, also delete the post from every social platform it was successfully published to. Pass a list of platform names (e.g. ["linkedin", "x"]) to delete from a subset only. TikTok does not expose a delete API and is never included. Omit or pass None to leave platform posts live. preserve_analytics: For published posts, keep metrics history and soft-delete the post. Defaults to True for published posts and False for drafts/scheduled. Pass False to force hard-delete. workspace_id: Workspace ID or name. Optional if user has only one workspace.
delete_post
Disconnect a connected social media account (SOFT DELETE). This is a soft-delete — the social_accounts doc stays in Firestore with `isActive: false`. Nothing on the user's social platform is touched: no OAuth revocation, no app uninstall, no post deletion. The user's posts and analytics history remain attached to the account, so if they reconnect later (via get_connection_urls) the same account stitches back into the workspace without losing history. Tell the user this clearly when they ask to disconnect — don't say "deleted" or "removed permanently," say "disconnected from Antwork." Scheduled posts targeting this account stay in place but won't publish until the user reconnects. Args: account_id: The social account document ID (from `list_social_accounts`). workspace_id: Workspace ID or name. Optional if user has only one workspace.
disconnect_social_account
Fetch recent posts from connected social media platforms. Retrieves actual published posts from your social accounts (not just Antwork drafts), including text, engagement metrics, publish date, and permalink. Useful for understanding what you've been posting and what's performing well. A workspace can have MULTIPLE accounts on the same platform (e.g., a personal LinkedIn + a brand LinkedIn). When that's the case, posts from every matching account are returned side-by-side in `accounts`, each tagged with its `accountId`. If the user is asking about a specific account, pass `account_id` (from `list_social_accounts`) to narrow the result. Platform limitations to know about (surface these to the user when relevant — they explain "why am I seeing no posts?" upfront): - LinkedIn personal accounts: NOT fetchable. LinkedIn closed the r_member_social scope, so this tool can only read posts from LinkedIn Organization pages, never from personal profiles. If the only connected LinkedIn account is personal, tell the user this before calling — they'll save a round trip. If you do call anyway, the response marks unfetched accounts with `unsupported: true` and an `unsupportedReason` you can quote. For Antwork-published LinkedIn posts on personal accounts: metrics still work (memberCreatorPostAnalytics), and you can see them via get_performance or get_post. It's only the "list the user's posts FROM LinkedIn" path that's blocked. Args: platform: Filter to a specific platform (e.g., "linkedin", "x"). If omitted, fetches from all connected platforms. account_id: Filter to a specific connected account. Use this when the workspace has multiple accounts on the same platform and the user means one in particular. max_posts: Maximum posts per account (1-25, default 10). workspace_id: Workspace ID or name. Optional if user has only one workspace.
fetch_platform_posts
Validate a signed-PUT upload and register it in the workspace media library. Second half of the LLM-on-CLI upload flow. Call this AFTER a successful curl PUT against the URL from `request_upload_url`. Verifies the GCS object exists, matches the declared mime + size, attaches a download token, and writes the workspace media record. When `attach_to_post_id` is set, also adds the uploaded media to that post, after whatever it already has. Nothing attached is removed (unlike `attach_media`, which sets the whole list). The attachment is best-effort: if it fails (e.g. wrong workspace), the response returns `attached: false` with `attachError`, but the media record still lands so you can retry `attach_media` cleanly. Args: gcs_path: The path returned by request_upload_url (under workspaces/{wid}/media/). Refused if it points outside this workspace's prefix. mime_type: Same value passed to request_upload_url. Checked against the object's stored Content-Type. byte_size: Same value passed to request_upload_url. Checked against the object's actual byte length. workspace_id: Workspace ID or name. Optional if user has only one workspace. attach_to_post_id: If set, add the new media URL to this post's existing media after registering.
register_uploaded_media
Get historical per-day engagement, as a tabular result. Returns a BigQuery-style payload in *long format* — one row per (date, platform) tuple, plus an "all" platform aggregate row per date. The host LLM can chart this directly (line per platform, stacked bars, pie of platform share, etc.) without a prebuilt visualization. Schema: - date (DATE), platform (STRING), likes, comments, shares, impressions (INTEGER), reachBasis (STRING), degraded (STRING) - "platform" values: each connected platform (e.g. "linkedin", "x") plus "all" for the daily total across platforms. A NULL is not a zero. A null in likes/comments/shares/impressions means that day was not measured for that platform — no rollup, or the platform does not report the field — and it must be drawn as a GAP, never as a point at zero. A 0 means the platform reported 0. `reachBasis` says what `impressions` counts on that row: "impressions", "views", "reach" or "reach_30d". Do not add impressions across rows with different bases, and do not compare them; they are different quantities. On the "all" row `impressions` is NULL whenever the day spanned more than one basis, because no such total exists. `degraded` lists fields whose 0 on that row is an artefact rather than a measurement — carried forward from a failed read, or rebased because the counter changed meaning that day. Exclude them from trend claims. Also returned at the top level: - `platforms`: list of platforms that appeared in the window - `platformTotals`: per-platform range totals - `rangeTotals`: workspace-wide range totals - `startDate` / `endDate`: window bounds This is the *real* day-by-day engagement earned during the window, not lifetime totals on posts published in the window. Numbers come from diffing cumulative metrics_history snapshots. Use this for: "how am I doing this week", "compare last week vs this week", "show me engagement over the last 30 days", "which day peaked". For per-post lifetime totals, use get_performance. Pass `post_id` for ONE post's history instead. The rows then carry cumulativeLikes / Comments / Shares / Impressions (totals as of that day) and dailyLikes / Comments / Shares / Impressions (earned since the previous row; NULL when unknowable), plus `spanDays` (how many days a daily value covers; older posts are read every few days, so do not chart a multi-day value as one day's spike) and `unreadableFields`. With `post_id` and no `days`, the post's full history comes back. Args: days: Trailing window in days (1–365, default 30). Includes today. post_id: One post's history instead of the workspace's. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_engagement_history
Get engagement per published post, per platform, as a tabular result. Returns a BigQuery-style payload: - `schema.fields`: column definitions (name, type, mode) - `rows`: one row per (post, platform) with that post's lifetime totals - `rowCount`: number of rows - `platformTotals`: totals PER PLATFORM — never summed across them NEVER ADD A COLUMN ACROSS PLATFORMS. `impressions` means something different on each: true impressions on linkedin (and x with elevated scope), views on threads/youtube/tiktok/instagram, reach on facebook pages, and a rolling 30-day window on pinterest that shrinks as a pin ages. Every row carries `reachBasis` saying which. Rows whose basis differs are not comparable and must not be added, averaged or ranked together. A blank cell means the platform does not report that metric — pinterest exposes no likes, youtube no shares, a linkedin personal profile no clicks. It does NOT mean zero. `degraded` lists counters carried forward from an earlier failed read rather than measured on this one. Args: limit: Max rows to return (default 30, max 50). days: Posts published in the last N days (default 30, max 365). platform: Restrict to one platform. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_performance
Get a single post by ID, including aggregated and per-platform metrics. Returns the post content, target platforms, status, and the latest LIFETIME engagement metrics: - `metrics`: aggregated totals across all platforms ({likes, comments, shares, impressions}) - `platformMetrics`: per-platform breakdown ({linkedin: {likes, comments, shares, impressions, ...}, x: {...}}) - `metricsUpdatedAt`: when these numbers were last refreshed - `hostSupportsUi`: True when the host renders MCP-App iframes — i.e. the response also renders an editable post-card. - `hostSupportsUiActions`: True when that card's buttons can call back into the server. ROUTE ON THIS ONE, not the flag above. Rendering a card and being able to press its Add media button are different capabilities, and ChatGPT has only the first: there the button paints and does nothing at all. Telling a ChatGPT user to "click Add media" is a dead end — and it is what this docstring used to say, because it routed on `hostSupportsUi`. See the media-attach routing in the server instructions for the per-host paths. For per-day historical engagement on this specific post (e.g. "what did this post earn each day for the last 14 days"), use get_engagement_history(post_id=...) instead. Args: post_id: The post ID. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_post
One-stop drafting bundle: brand identity, voice, and recent posts. Call this BEFORE writing a post. It returns everything the model needs to produce on-brand, on-tone copy for a specific (workspace, platform, account) target in a single round-trip: - `workspace`: name, website, logoUrl, brandVersion - `account`: platform, accountId, accountName, accountHandle - `recentPosts`: up to 15 recent published posts on this account, each with its engagement metrics. These ARE the voice — read them and match how the account writes, weighting the ones that performed. They also tell you which themes not to repeat. Empty means no history to imitate: ask the user for samples instead of inventing. - `hostSupportsUi`: True when the host renders MCP-App iframes (claude.ai etc). Use for media-attach routing later in the flow: on UI hosts, `get_post`/`create_post` render an Add media button the user can drop a file on; on CLI hosts, use the signed-URL flow instead. Workspace identity (`name + website + logoUrl + brandVersion`) is safe to cache in host memory keyed by `antwork:ws:<workspaceId>:brand`. Re-fetch when `brandVersion` differs from the value in `get_workspace_settings`. Args: platform: Platform key ("linkedin", "x", "facebook", "instagram", "threads", "tiktok", "youtube", "pinterest"). Optional — if omitted, this returns a `connectedPlatforms` error that lists the workspace's connected platforms and active accounts, so you can retry with one of them in a single hop. account_ids: SEVERAL accounts at once, for drafting a campaign. Pass every account you are about to write for and the response carries an `accounts` list, each entry with that account's own recent posts. One call instead of one per account, which matters because each of those hits the platform's API. Fewer posts are returned per account when you ask for several, so the bundle stays a readable size — enough to hear each voice, and it is still per-account. Overrides `platform` / `account_id` when given. account_id: Specific social account ID. Optional — if omitted and the platform has exactly one active account, it's used. If the platform has MORE THAN ONE active account (e.g. a LinkedIn org page + a personal profile), this returns `error: "account_ambiguous"` with the candidates in `ambiguousAccounts` instead of guessing — ask the user which one, then retry with account_id. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_post_context
Open the connections panel to connect, disconnect, or re-auth accounts. This renders the interactive connections-panel iframe (connect grid + per-account reauth / disconnect controls) and returns OAuth connect URLs per platform. Call this whenever the user wants to MANAGE connections — connect a new account, reconnect a stale one, or see the connect options. For just reading what's already connected, use the lighter `list_social_accounts` (no iframe). On hosts without UI, present the returned URLs as clickable links. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_connection_urls
Get the workspace's configuration: name, default posting schedule, timezone, and connected platforms. Use this to discover the workspace's preferred posting times and timezone before suggesting schedule slots, or to confirm which platforms the user has selected as their primary publishing targets. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_workspace_settings
List the social media accounts currently connected to the workspace. USUALLY UNNECESSARY for picking an `account_id`. Every workspace-scoped tool result already carries `workspaceAccounts` — `{id, platform, accountName, isActive}` for each connected account. Read that from the newest result you have. Call this when you need a field it omits (handles, token health, avatars), or when nothing recent carries the list. Text-only and fast — this does NOT render the connections panel iframe. Use it to answer account questions and to confirm target platforms are connected before drafting / scheduling / publishing. When the user wants to actually connect, disconnect, or re-authenticate an account, call `get_connection_urls` instead — that one renders the interactive connections panel (connect grid + per-account reauth / disconnect). Returns one entry per active connection with platform, account name, handle, account ID, token health, and a per-account `reauthUrl` the user can open to refresh the OAuth token. Call get_connection_urls if any expected platform is missing. Account state is mutable across turns — the user may connect or disconnect accounts in the connections panel without telling you. Other tools return a `connectionsModifiedAt` stamp; when it's newer than the one you got from your most recent list_social_accounts, the cached account list is stale and you should re-call this tool before answering. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
list_social_accounts
List media files in the workspace media library. Returns uploaded images, videos, and PDF documents with URLs that can be attached to posts. Use this to find existing media before creating posts for visual platforms like Instagram, TikTok, or Pinterest, or for LinkedIn document posts. For libraries larger than `limit`, the response includes `nextCursor` — pass it back as `cursor` to fetch the next page. `nextCursor` is null when there are no more items. Args: media_type: Filter by type: "image", "video", "document" (PDFs), or None for all. limit: Max items per page (default 30, max 100). workspace_id: Workspace ID or name. Optional if user has only one workspace. cursor: Pagination cursor from a previous call's `nextCursor`. Omit for the first page.
list_media
List the workspace's posts, optionally filtered. Sorted scheduled (soonest first) → draft → published → failed, the rest newest-first. The posts-table iframe pages 8 rows at a time, so the default of 50 lets the user flip pages without another call. Args: status: "draft", "scheduled", "published", or "failed". query: Keyword matched against post text, case-insensitive. platform: e.g. "linkedin", "x". campaign_id: Only the posts of one campaign (from create_campaign). date_from: Scheduled on or after this date (ISO 8601, e.g. "2026-03-01"). date_to: Scheduled on or before this date (ISO 8601). limit: Max posts to return (default 50, max 50). workspace_id: Workspace ID or name. Optional if user has only one workspace.
list_posts
List all workspaces the authenticated user belongs to. USUALLY UNNECESSARY. Every workspace-scoped tool result already carries `workspaceHeader.workspaces` — the same list, with `id`, `name` and `isDefault`. Read that from the newest result you have instead of calling this. Reach for this tool only when you have no recent result to read, or when you need a field the piggybacked list omits (`logoUrl`, `website`). If the user has only one workspace, other tools auto-select it. Each entry is flagged with `isDefault: true` when it matches the user's persisted default workspace — that's the one any tool will auto-select when `workspace_id` is omitted.
list_workspaces
IFRAME-INTERNAL. DO NOT CALL FROM CHAT. Loads what the post card's settings panel needs that the post itself does not carry: a Pinterest account's boards, or a TikTok creator's current audience options. Both are live lookups against the platform, so they run when the user opens the panel rather than on every card render. Args: post_id: The post whose settings panel is open. workspace_id: Workspace ID or name. from_ui_bridge: Set ONLY by the iframe bridge. Never by the LLM.
get_post_settings
Publish every post in a campaign right now, in one call, and wait for the platforms to answer. Prefer this over calling publish_post once per post. Each publish_post waits out its own polling window, so four of them in a row can take four times as long as one and exceed the host's tool timeout. This dispatches all of them at once and polls the whole set together, so the wait is the same as for a single post. A post that cannot publish is reported on its own and the rest still go. Live URLs come back per post in `posts[].url`. Args: campaign_id: The campaign to publish. Returned by create_campaign. post_ids: Explicit posts instead of a whole campaign. workspace_id: Workspace ID or name. Optional if user has only one workspace.
publish_campaign
Publish a draft post immediately to its target account. Call this after create_post to publish right away instead of scheduling for later. Waits for the publish to complete on the target platform (up to about half a minute) and returns the live post URL in `url`. Publishing several posts? Call publish_campaign instead. Each publish_post waits out its own polling window, so running them back to back multiplies that wait; publish_campaign dispatches them together and waits once. Args: post_id: The post ID returned by create_post. workspace_id: Workspace ID or name. Optional if user has only one workspace.
publish_post
IFRAME-INTERNAL. DO NOT CALL FROM CHAT. Used by the MCP-App iframes to report their own failures. The iframes run under the host's `connect-src 'self'` CSP from an opaque origin, so they cannot reach PostHog, an error endpoint, or anything else directly — the postMessage bridge is their only egress, which is why this is a tool. The LLM must NEVER call this tool. It reports nothing when `from_ui_bridge` is unset, so a stray call cannot fabricate an issue. Args: message: The error message as the browser reported it. scope: Dotted call-site identifier, e.g. `post_card.attach_media`. This is the grouping key and the actionable field — the bundles are minified with no usable source maps. app: Which iframe reported, e.g. `post-card`. stack: Browser stack trace, if any. Carried as a property only. from_ui_bridge: Set ONLY by the iframe bridge. Never by the LLM.
report_ui_error
Mint a short-lived signed PUT URL for a direct upload to GCS. PREREQUISITE: do NOT call this until you have a tool response showing `hostSupportsUi: false` (typically from a prior `get_post(post_id)` or `get_post_context` call). This flow needs a shell to `curl` with, and no host that renders a panel gives the model one. On `hostSupportsUi: true` hosts, check `hostSupportsUiActions` to see what to do instead: - `true` (the Claude apps, web and desktop): use the post-card's Add media button — `get_post(post_id)` renders the card and its picker uploads + auto-attaches via `update_post` in one step. - `false` (ChatGPT): the card's buttons cannot call back either, so there is no picker AND no shell. Use `upload_media_inline` for a chat-attached file, `upload_media` for a public URL, or send the user to the web app's Media page and finish with `list_media` + `attach_media`. Skipping that check and firing this tool first is a common mistake that wastes a round-trip and confuses the user. LLM-on-CLI path (hostSupportsUi=false, shell available): call this, then run curl -X PUT --upload-file <local_path> \ -H 'Content-Type: <mime_type>' '<signedUrl>' On HTTP 200, call `register_uploaded_media(gcs_path, mime_type, byte_size, attach_to_post_id=…)` to finalize the upload, attach it to a post in the same step, and surface the media in the workspace library. Bytes go LLM→GCS direct, never entering the chat transcript. Supported mime types: - Images: image/png, image/jpeg, image/gif, image/webp, image/avif (≤ 10 MB) - Videos: video/mp4, video/quicktime, video/webm (≤ 100 MB) - Documents: application/pdf (≤ 100 MB) Args: mime_type: One of the supported mime types above. byte_size: Exact file size in bytes. Must match the actual file — `register_uploaded_media` verifies the uploaded blob is this size. workspace_id: Workspace ID or name. Optional if user has only one workspace.
request_upload_url
Retry a post that failed to publish. Args: post_id: The failed post ID to retry. workspace_id: Workspace ID or name. Optional if user has only one workspace.
retry_failed_post
IFRAME-INTERNAL. DO NOT CALL FROM CHAT. Saves the TikTok settings a person chose on the post card. TikTok requires the creator to choose the audience themselves, per post, from the options it returns for their account, so this is the one write path for them and it refuses any call that did not come from the card. Args: post_id: The TikTok post. privacy_level: One of the creator's `privacyLevelOptions`. allow_comments: Whether viewers may comment. branded_content: The video promotes a third party. brand_organic: The video promotes the creator's own business. ai_generated: The video is AI-generated content. workspace_id: Workspace ID or name. from_ui_bridge: Set ONLY by the iframe bridge. Never by the LLM.
set_tiktok_post_options
Schedule every post in a campaign for the same time, in one call. Renders nothing, like schedule_post. A post that cannot be scheduled — over the character limit, missing required media, disconnected account — is reported on its own and the rest still go. Args: scheduled_for: ISO 8601 datetime, at most 30 days ahead. Applied to every post in the set. campaign_id: The campaign to schedule. Returned by create_campaign. post_ids: Explicit posts instead of a whole campaign. workspace_id: Workspace ID or name. Optional if user has only one workspace.
schedule_campaign
Schedule a draft post for automatic publishing at a specific time. Call this after create_post to actually schedule the post. The post must exist and be bound to a social account. Args: post_id: The post ID returned by create_post. scheduled_for: ISO 8601 datetime (e.g. "2026-04-01T10:00:00Z"). Must be in the future and at most 30 days ahead. workspace_id: Workspace ID or name. Optional if user has only one workspace.
schedule_post
Pin (or clear) the user's default workspace for subsequent tool calls. Pass a workspace ID or name to PIN: every workspace-scoped tool called without an explicit `workspace_id` thereafter resolves to this workspace — instead of erroring out with "multiple workspaces". Use this the moment the user names which workspace they want to work in for the conversation; you won't need to thread `workspace_id` through every follow-up call. Pass `null` (or an empty string) to CLEAR the default. The next ambiguous tool call will again return "multiple workspaces" until a new default is set. Use this when the user says "stop defaulting to X" or wants to be prompted each time. The default persists across conversations and clients (it lives on the user's account). Calling this with a different ID overwrites the previous one — no clearing required first. An explicit `workspace_id` on any individual tool call always overrides the default for that one call without changing what's persisted. Args: workspace_id: Workspace ID or name to make the default. Pass null or an empty string to clear the persisted default.
set_default_workspace
Update an existing post. Posts are single-account. To switch the target account, pass `account_id` and we re-bind the post (the platform is re-derived from the new account). There is no per-platform text variant — if the user wants the same idea on a different account with different copy, call `create_post` for a new draft (optionally sharing `campaign_id` to link them). The post's card is already on screen from create_post / get_post, so this call renders a one-line "updated" note instead of a second card, and the card above refreshes to the new version where the client allows it. Batch your changes into one call rather than several small ones. Args: post_id: The post ID to update. text: New post text. account_id: Optional — switch the post's target social account (also re-derives `platform`). Must be a social_account doc ID in this workspace, from list_social_accounts. hashtags: Updated hashtags without # prefix. A list; a comma- or space-separated string is also accepted. goal: Updated goal. status: "draft" or "scheduled". scheduled_for: ISO 8601 datetime for scheduling. campaign_id: Optional grouping ID; reuse across related drafts. pinterest_board_id: Pinterest posts only. Set the board the pin publishes to, an id from `boards` in get_post_context. platform_options: Per-platform settings to change, merged into the post's existing ones (see create_post for the keys). Pass a setting as null to clear it back to the platform default. TikTok settings are not accepted — the user sets them on the card. workspace_id: Workspace ID or name. Optional if user has only one workspace.
update_post
Update the workspace's identity (name, website, logo) or its settings. Only the fields passed change; everything else is left as it is. Args: name: The workspace / brand name (50 characters max). website: Brand website URL. logo_url: URL to the brand logo image. timezone: IANA timezone for scheduling, e.g. "Europe/Madrid". preferred_times: Preferred posting times as HH:MM, e.g. ["09:00", "18:00"]. tag_outbound_links: Append UTM parameters to links on the workspace's own website when a post publishes, so clicks stop reading as "direct" traffic. Needs `website` set; no other site's links are touched. workspace_id: Workspace ID or name. Optional if user has only one workspace.
update_workspace
Upload file bytes directly. Prefer any other path that works. Primarily used by the post-card "Add media" picker, which sends bytes over the MCP RPC channel (postMessage → tools/call) without touching the LLM's transcript. Do NOT call this from chat when the host has a cheaper path, because a large base64 tool argument balloons the conversation context and is orders of magnitude slower: - `hostSupportsUiActions: true` → the post-card picker already does this for you; just tell the user to click Add media. - `hostSupportsUi: false` → you have a shell; use `request_upload_url` + `curl PUT` + `register_uploaded_media`, which streams bytes LLM→GCS direct. - ChatGPT (`hostSupportsUi: true`, `hostSupportsUiActions: false`) → for anything the user attached to the conversation, call `attach_chatgpt_file(file=..., attach_to_post_id=<id>)`. ChatGPT fills `file` in and Antwork downloads the bytes server-side, so they never enter the transcript and a 100 MB video costs the same context as a 10 KB one. That leaves one case for this tool from chat: bytes you already hold and cannot get any other way — a small image, on a host with no picker callback and no shell. Base64 in a tool argument is opaque to the host, so ChatGPT shows the user a "suspicious content: opaque payload" consent prompt before every call. That warning is correct: it cannot see what is in the string. Reaching for `attach_chatgpt_file` first is what keeps the user from having to answer it. For anything large, send the user to the web app's Media page and finish with `list_media` + `attach_media`. Args: image_base64: Base64-encoded file payload (no `data:` prefix). mime_type: MIME type (e.g. "image/png", "video/mp4"). workspace_id: Workspace ID or name. Optional if user has only one workspace. thumbnail_base64: Optional first-frame preview for video uploads (raw base64). Ignored for non-video uploads. from_ui_bridge: Set ONLY by the post-card iframe's Add media picker over the postMessage RPC bridge — never by the LLM. Marks the call as iframe-originated so it's accepted regardless of host name.
upload_media_inline
Add a file to the workspace media library from a public HTTPS URL. Antwork downloads the URL server-side and stores it. Use this for AI-generated content or anything else already reachable over HTTP. For LOCAL files (the user attached a screenshot, photo, video, etc.): NEVER read the file and pass it through here as base64 — that bloats the chat transcript and is extremely slow. Use the signed-URL flow instead: 1. `request_upload_url(mime_type, byte_size)` → returns `signedUrl` and `gcsPath`. 2. `curl -X PUT --upload-file <local_path> -H 'Content-Type: <mime_type>' '<signedUrl>'` 3. `register_uploaded_media(gcs_path, mime_type, byte_size)` — optionally with `attach_to_post_id` to attach to a draft in one step. Bytes go LLM→GCS direct via the signed URL — they never ride the transcript. Supported types: - Images: image/png, image/jpeg, image/gif, image/webp, image/avif - Videos: video/mp4, video/quicktime, video/webm - Documents: application/pdf (LinkedIn document posts only) Args: image_url: Public HTTPS URL of the file. Despite the name, accepts images, videos, and PDFs. mime_type: MIME type (e.g. "image/png", "application/pdf", "video/mp4"). workspace_id: Workspace ID or name. Optional if user has only one workspace.
upload_media
Get scheduled and published posts as an interactive calendar view. Returns posts grouped by date, useful for seeing what's coming up and finding gaps in the content schedule. On supported clients this renders an interactive calendar UI. Args: date_from: Start date (ISO 8601, e.g., "2026-03-24"). Defaults to today. date_to: End date (ISO 8601, e.g., "2026-03-31"). Defaults to 7 days from start. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_calendar
Get the current authenticated user's profile. Returns user ID, email, display name, subscription status, and the persisted default workspace (if set).
whoami
Antwork ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Antwork's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Antwork alternatives on ChatGPT?
As of 2026-10-21, Antwork competes with 8digit Planner, Abency, Adzviser Sprout Social, Breakreach, BulkPublish, Buzzfy, Cambiante: Content Manager, Content Plan by Promoplug and 76 more in ChatGPT Social Media Management & Scheduling, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.