addItemToCollections
ChatGPTPURPOSE: Add an item to one or more Collections in the current workspace. A Collection is a workspace-scoped tag set (a saved multi-select tag) applied to items; adding an item to a Collection tags it, so the item then appears when that Collection is used as a filter. INPUT: - itemId (required string): the Archive item id to tag. - collectionIds (array of strings, default []): Collection ids to add the item to. - collectionNames (array of strings, default []): Collection names to add the item to, matched case-sensitively within the workspace. - autoCreate (boolean, default false): when true, a name in collectionNames that does not match an existing Collection creates a new Collection; when false, unmatched names are dropped and, if NO collection resolves at all, the call returns a "No valid collections found" userError. You MUST provide at least one of collectionIds or collectionNames. Providing neither returns a userErrors entry ("Provide 'collectionIds' or 'collectionNames'") and makes no change. RESOLVING IDS vs NAMES: prefer ids. Resolve Collection ids with getCollections first, then pass them as collectionIds. Use the ids returned by getCollections for THIS workspace: several id forms are accepted depending on how the workspace stores Collections. An id from another workspace, or one this workspace cannot write through this API, rejects the WHOLE call (all-or-nothing) with a userErrors entry naming the offending id under collectionIds. An id that exists in the workspace but is not writable here reports "exists in this workspace but is not writable through this API" — re-resolve via getCollections and retry with the id it returns. AUTO_CREATE: use autoCreate: true only when creating a Collection is intended. It creates the new Collection in this workspace's own Collection storage. A blank name is rejected. Resolve names via a read tool first when you mean to reference an existing Collection — a typo with autoCreate: true silently creates an unwanted Collection. OUTPUT: { item: { id, customAttributes } | null, userErrors: [{ field, message }] }. item is the item as stored after the write. In workspaces whose Collections use the older storage, item.customAttributes.collections does NOT reflect Collection membership and is returned unchanged — use getCollection(id:).itemCount to confirm the write. In preset-backed workspaces that array holds the item's Collections after the write, as option ids — not the ids getCollections returns; match them by name through getCustomAttributeSchemas (entity ITEM, key "collections"). userErrors[].field is an array of path segments (e.g. ["collectionIds"]). VERIFY AFTER WRITE: PRIMARY (works in every workspace): getCollection(id: "<collection-id>") — its itemCount reflects the change immediately. Default to this check. SECONDARY (works in every workspace): searchItems(filter: { collectionsIds: ["<collection-id>"] }) with a Collection id from getCollections, and check the item is present. Search results lag a write by up to about a minute, so this is confirmation, not the immediate check. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create* /…
addLabelsToCreators
ChatGPTPURPOSE: Assign labels to creators in the current workspace. Labels are set membership keyed by name: assigning a label a creator already carries is a success no-op, so repeating the call is safe. WHICH TOOL: up to 40 creators → this tool (synchronous). More → bulkAddLabelsToCreators (background operation, up to 1000 per call, a flat 250 points). INPUT: - creatorIds (required array of strings, 1-40): creator ids from searchCreators or getCreator. If any id does not resolve in this workspace the WHOLE call is rejected (all-or-nothing, nothing written) with a userError on ["creatorIds"] naming it. - labelNames (required array of strings, 1-50, each at most 100 characters): label names; matching ignores case and surrounding whitespace. MINT ON ASSIGN: Assigning a label that does not exist yet creates it. Check getLabels first when you mean an existing label; a typo adds a new one. A label with deleting: true is being removed; do not assign it, and do not recreate it until it disappears from getLabels. OUTPUT: { creators: [{ id, customAttributes }], userErrors: [{ field, message }] }. creators is empty when userErrors is non-empty. A label being deleted is a userError on ["labelNames"]; a workspace with no user to attribute the write to is one on ["base"]. Lock contention with another write is an error to retry, not a userError. COST: 50 rate-limit points for the first creator plus 100 for each additional one. A call costing more than the workspace's rate-limit capacity is a userError on ["creatorIds"] naming the largest call that fits; nothing is written or charged. LABEL WRITE LIMIT: a workspace labels at most 40 creators at once, then one creator every 2 seconds, shared by every caller. Past it the call is an error "Label write limit exceeded: … Retry after N seconds." — wait N seconds, then retry. VERIFY AFTER WRITE: call getCreator(id:) and read the labels key of the returned customAttributes JSON object — it is a path inside the JSON value, never a selection set; call getLabels as well when a name may have been newly created. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retr…
bulkAddLabelsToCreators
ChatGPTPURPOSE: Assign labels to many creators in the current workspace as a background operation. Labels are set membership keyed by name: a creator already carrying a label is left as is, so repeating the call is safe. WHICH TOOL: up to 40 creators → addLabelsToCreators (synchronous, returns the creators). More → this tool, up to 1000 creators per call; split larger sets into several calls. INPUT: - creatorIds (required array of strings, 1-1000): creator ids from searchCreators or getCreator. An id that does not resolve in this workspace is skipped and returned in skippedCreatorIds; the rest are scheduled. - labelNames (required array of strings, 1-50, each at most 100 characters): label names; matching ignores case and surrounding whitespace. MINT ON ASSIGN: a name that matches no label creates it. Check getLabels first when you mean an existing label; a typo adds a new one. Do not assign a label with deleting: true. COST: a flat 250 rate-limit points per call, whatever the creator count. IN FLIGHT: at most 2 creator label bulk operations — including bulk edits made in the Archive app — run per workspace at once; a third is a userError on ["base"] naming the running ones. Wait for one to finish, then retry. OUTPUT: { scheduled, operationId, acceptedCount, skippedCreatorIds, userErrors: [{ field, message }] }. scheduled is false with a null operationId when userErrors is non-empty or no id resolved. A label being deleted is a userError on ["labelNames"]. VERIFY AFTER WRITE: poll getOperation(id: operationId) until status is COMPLETED, PARTIAL or FAILED; read per-creator failures with getOperationRecords; then spot-check getCreator(id:) and read the labels key of its customAttributes. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
createCollection
ChatGPTPURPOSE: Create a Collection (a saved tag set) in the current workspace. A Collection is a workspace-scoped multi-select tag applied to items; once created you tag items into it with addItemToCollections and filter items by it. INPUT: - name (required string): the display name for the new Collection. Names are UNIQUE within the workspace. NAME UNIQUENESS: if a Collection with the given name already exists, the call returns a duplicate-name userError (field ["input","name"]) and creates NOTHING. Do NOT invent a name variant ("Summer 2", "Summer_new") to work around it — REUSE the existing Collection instead: look it up with getCollections, take its id, and proceed with that id. AVAILABILITY: creating a Collection is only possible in workspaces whose Collections use the standard storage. In a workspace that stores Collections in an older format the call returns a schema-missing userError and creates nothing; there, create the Collection by tagging an item into it directly — addItemToCollections with collectionNames plus autoCreate: true creates the named Collection as part of the tagging call. OUTPUT: { collection: { id, name, itemCount } | null, userErrors: [{ field, message }] }. collection.id is the Collection's id in this workspace — pass it to addItemToCollections / removeItemFromCollections to tag items, and to searchItems(presetId:) or getCollection(id:) to read it. collection is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","name"]). VERIFY AFTER WRITE: confirm the Collection landed with getCollection(id:) using the returned collection.id. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
createContentView
ChatGPTPURPOSE: Create a saved Content View in the current workspace. A Content View stores a filter set once; read its items later with searchItems(presetId:) instead of re-sending filters each call. COMPOSE WITH filter / sorting (typed, as in searchItems). To copy a view, send its getContentView filters, sort, and customAttributeConditions verbatim. INPUT: - name (required string): the display name. Names are NOT required to be unique — two content views may share a name. - filter (optional object): the typed filter (the same object searchItems filter takes), e.g. { "provider": "TIKTOK", "followersCount": { "from": 10000 } }. Stored as the equivalent filters blob. Send exactly one of filter / filters. - filters (optional object): the opaque filter blob, for cloning a get output verbatim (see VIEW FILTERS below). Pass {} for a view with no filters (all items). Send exactly one of filter / filters. - customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE FILTERS below). Defaults to []. - sorting (optional array): the typed sort (the same list searchItems `sorting` takes), e.g. [{ "sortKey": "LIKE_COUNT", "sortOrder": "DESC" }]. Send at most one of sorting / sort. - sort (optional array): sort directives for cloning a get output verbatim, e.g. [{ "field": "taken_at", "direction": "desc" }]. Defaults to []. - showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults to true. OUTPUT: { contentView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. contentView.id is the view's canonical FilterPreset UUID — pass it to searchItems(presetId:) to read its items, or to getContentView(id:) to read it back. contentView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","name"]). VERIFY AFTER WRITE: confirm the view landed with getContentView(id:) using the returned contentView.id. VIEW FILTERS (the filters blob): filters is a JSON object mapping filter keys to values, stored verbatim. Pass {} for "no filters". Unknown keys pass through. Enum-backed keys take the same enum names as searchItems filter. Stored values (such as "tiktok" or "Approved") are also accepted, in any letter case. An invalid value is rejected with a userError. To copy an existing view, clone its filters verbatim (fetch it with the matching get tool). createContentView / updateContentView also take a typed `filter` (the same object searchItems `filter` takes) and `sorting`; PREFER them when composing a content view, and use `filters` / `sort` only to clone a get output verbatim. CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types: array of FilterItemType names: POST, REEL, STORY, TIKTOK, TIKTOK_STORY, YOUTUBE, YOUTUBE_SHORT. - provider: one Provider name: INSTAGRAM, TIKTOK, INTERNAL, YOUTUBE. - content_types: array of MediaContentType names: IMAGE, VIDEO. - virality_score: array of FilterViralityScore names: HIGH, MEDIUM, LOW, NOT_VIRAL. - import_type: one FilterImportType name: MANUAL, AUTOMATIC. - social_profile_account_types: array of SocialProfileAccountType names: PERSONAL, CREATOR, BUSINESS. - usage_rights_status, instagram_whitelisting_status, tiktok_spark_code_status: each an array of FilterContractStatus names: APPROVED, EXPIRED, IN_QUEUE, REJECTED, REQUESTED. - tiktok_whitelisting_eligibility: one FilterTikTokWhitelistingEligibility name: ALL. - engagement: array of range entries whose "field" is a FilterEngagementField name: LIKE_COUNT, COMMENT_COUNT, MERGED_VIEW_PLAY_COUNT, SHARE_COUNT, ENGAGEMENTS_RATE, IMPRESSIONS_RATE, EARNED_MEDIA_VALUE. - super_search: object whose "mode" is a FilterSuperSearchMode name: EMBEDDING_CONTENT, FUZZY_TRANSCRIPTION, FUZZY_CAPTION. - taken_at: publication-date range { "from": ISO8601, "to": ISO8601 }. - tags_names: array of hashtag / mention tag names. - campaigns_ids / collections_ids (from getCollections) / so…
createCreatorView
ChatGPTPURPOSE: Create a saved Creator View in the current workspace. A Creator View saves a reusable narrowing (customAttributeConditions + sort) once; read its creators later with creators(presetId:). NOTE: the filters blob is stored and echoed back but NOT applied on read — creators(presetId:) narrows ONLY by customAttributeConditions + sort (see VIEW FILTERS below). Use customAttributeConditions to narrow results. CLONE FIRST (strongly preferred): the filters blob is opaque and easy to get subtly wrong. Before composing filters from scratch, fetch an existing Creator View with getCreatorViews (or getCreatorView(id:)), copy its filters value, and adapt it. A cloned-and-tweaked blob renders correctly in the app; a hand-built one may look successful here yet render broken. Only build filters from scratch for simple views, using the VIEW FILTERS contract below. INPUT: - name (required string): the display name. Names are NOT required to be unique — two Creator Views may share a name. - filters (required object): the opaque filter blob (see VIEW FILTERS below) — stored and echoed back but NOT applied on read. Pass {} unless cloning an existing view verbatim. - customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE FILTERS below). Defaults to []. - sort (optional array): sort directives, e.g. [{ "field": "created_at", "direction": "DESC" }]; field must be created_at, full_name or id (id always ascending), else a userError on ["input","sort"]. Defaults to []. - showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults to true. OUTPUT: { creatorView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. creatorView.id is the view's canonical FilterPreset UUID — pass it to creators(presetId:) to read its creators, or to getCreatorView(id:) to read it back. creatorView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","name"]). VERIFY AFTER WRITE: confirm the view landed with getCreatorView(id:) using the returned creatorView.id. VIEW FILTERS (the filters blob): filters is a JSON object mapping filter keys to values, stored verbatim. Pass {} for "no filters". Unknown keys pass through. Enum-backed keys take the same enum names as searchItems filter. Stored values (such as "tiktok" or "Approved") are also accepted, in any letter case. An invalid value is rejected with a userError. To copy an existing view, clone its filters verbatim (fetch it with the matching get tool). createContentView / updateContentView also take a typed `filter` (the same object searchItems `filter` takes) and `sorting`; PREFER them when composing a content view, and use `filters` / `sort` only to clone a get output verbatim. CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types: array of FilterItemType names: POST, REEL, STORY, TIKTOK, TIKTOK_STORY, YOUTUBE, YOUTUBE_SHORT. - provider: one Provider name: INSTAGRAM, TIKTOK, INTERNAL, YOUTUBE. - content_types: array of MediaContentType names: IMAGE, VIDEO. - virality_score: array of FilterViralityScore names: HIGH, MEDIUM, LOW, NOT_VIRAL. - import_type: one FilterImportType name: MANUAL, AUTOMATIC. - social_profile_account_types: array of SocialProfileAccountType names: PERSONAL, CREATOR, BUSINESS. - usage_rights_status, instagram_whitelisting_status, tiktok_spark_code_status: each an array of FilterContractStatus names: APPROVED, EXPIRED, IN_QUEUE, REJECTED, REQUESTED. - tiktok_whitelisting_eligibility: one FilterTikTokWhitelistingEligibility name: ALL. - engagement: array of range entries whose "field" is a FilterEngagementField name: LIKE_COUNT, COMMENT_COUNT, MERGED_VIEW_PLAY_COUNT, SHARE_COUNT, ENGAGEMENTS_RATE, IMPRESSIONS_RATE, EARNED_MEDIA_VALUE. - super_search: object whose "mode" is a FilterSuperSearchMode name: EMBEDDING_CONTENT, FUZZY_TRANSCRIPTION, FUZZY_CAPTION. - taken_at: publication-date …
createLabels
ChatGPTPURPOSE: Creates labels in the workspace's creator label taxonomy. Names already present (ignoring case and surrounding whitespace) are returned unchanged — creating an existing label is a no-op, not an error. Repeating the call is safe. Creating a label does not assign it to anyone; use addLabelsToCreators for that. Labels have no id; the name is the identity. INPUT: - labelNames (required array of strings, 1-50, each at most 100 characters): the names to create. A blank name is a userError on ["labelNames"]. BEING DELETED: A label with deleting: true is being removed; do not assign it, and do not recreate it until it disappears from getLabels. Creating such a name is a userError. OUTPUT: { labels: [{ name, deleting }], userErrors: [{ field, message }] }. labels holds the stored spelling of every submitted name, one entry per name ignoring case, and is empty when userErrors is non-empty. Lock contention with another write is an error to retry, not a userError. VERIFY AFTER WRITE: call getLabels — the returned spellings are listed immediately. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
createSocialProfileView
ChatGPTPURPOSE: Create a saved Social Profile view in the current workspace. A Social Profile view stores a filter set once; read its profiles later with getSocialProfiles(presetId:) instead of re-sending filters each call. CLONE FIRST (strongly preferred): the filters blob is opaque and easy to get subtly wrong. Before composing filters from scratch, fetch an existing Social Profile view with getSocialProfileViews (or getSocialProfileView(id:)), copy its filters value, and adapt it. A cloned-and-tweaked blob renders correctly in the app; a hand-built one may look successful here yet render broken. Only build filters from scratch for simple views, using the VIEW FILTERS contract below. INPUT: - name (required string): the display name. Names are NOT required to be unique — two Social Profile views may share a name. - filters (required object): the opaque filter blob (see VIEW FILTERS below). Pass {} for a view with no filters (all profiles). - customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE FILTERS below). Defaults to []. - sort (optional array): sort directives. Defaults to []. - showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults to true. OUTPUT: { socialProfileView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. socialProfileView.id is the view's canonical FilterPreset UUID — pass it to getSocialProfiles(presetId:) to read its profiles, or to getSocialProfileView(id:) to read it back. socialProfileView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","name"]). VERIFY AFTER WRITE: confirm the view landed with getSocialProfileView(id:) using the returned socialProfileView.id. VIEW FILTERS (the filters blob): filters is a JSON object mapping filter keys to values, stored verbatim. It is OPAQUE and unvalidated on write — an invalid blob is accepted but may render the view broken in the app. STRONGLY PREFER cloning an existing view's filters (fetch it with the matching get tool) over composing from scratch. Pass {} for "no filters". CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types : array of item types, e.g. ["POST","REEL","STORY","SHORT"]. - provider : one of "INSTAGRAM","TIKTOK","YOUTUBE". - content_types : array of media types, e.g. ["IMAGE","VIDEO"]. - taken_at : publication-date range { "from": ISO8601, "to": ISO8601 }. - virality_score : array of "VIRAL","HIGH","MEDIUM","LOW". - tags_names : array of hashtag / mention tag names. - campaigns_ids : array of campaign ids. - collections_ids : array of Collection ids (as returned by getCollections). - social_profile_ids : array of social profile ids. - account_names : array of social profile handles. - followers_count : follower-count range { "from": int, "to": int }. SOCIAL-PROFILE-VIEW `filters` (createSocialProfileView / updateSocialProfileView): the blob is stored and echoed back by the get tools, but it is NOT applied when reading profiles — socialProfiles(presetId:) / getSocialProfiles(presetId:) filter ONLY by the view's customAttributeConditions and sort. To make a Social Profile View that actually narrows results, use customAttributeConditions (see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters unless cloning an existing view verbatim. CREATOR-VIEW filters (createCreatorView / updateCreatorView): same caveat — the blob is stored and echoed back by the get* tools, but it is NOT applied when reading creators. creators(presetId:) / searchCreators(presetId:) narrow ONLY by the view's customAttributeConditions and sort. To make a Creator View that actually narrows results, use customAttributeConditions (see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters unless cloning an existing view verbatim. Example content-view filters: { "item_types": ["REEL"], "provider": "INSTAGRAM", "taken_at": { "from": "2024-01-01", "to": "2024-12-31" } }. F…
createViewGroup
ChatGPTPURPOSE: Create a user-created view group in the current workspace. A view group is a workspace-scoped sidebar bucket that Content Views, Social Profile Views, and workspace Creator Views can be moved into (with moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup). CAMPAIGN Creator Views are NEVER group members — they are campaign-scoped, not workspace-scoped. INPUT: - name (required string): the display name for the new group. Names are UNIQUE within the workspace. NAME UNIQUENESS: if a group with the given name already exists, the call returns a validation_error userError (field ["input","name"]) and creates NOTHING. Do NOT invent a name variant ("Team 2", "Team_new") to work around it — REUSE the existing group instead: look it up with getViewGroups, take its id, and move views into it with the move tools. OUTPUT: { viewGroup: { id, name, contentViews, socialProfileViews, creatorViews, collections } | null, userErrors: [{ field, message }] }. viewGroup.id is the group's canonical UUID — pass it as groupId to moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup to fill it, to reorderViewsInGroup to order its members, or to getViewGroup(id:) to read it back. A freshly created group has empty contentViews / socialProfileViews / creatorViews / collections. viewGroup is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","name"]). VERIFY AFTER WRITE: confirm the group landed with getViewGroup(id:) using the returned viewGroup.id. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
createWebhookSubscription
ChatGPTPURPOSE: Create an outbound-webhook subscription in the current workspace. New items landing in the subscribed content view(s) trigger a signed HTTPS POST to your endpoint. INPUT: - name (required string): display name for the subscription. - url (required string): the HTTPS endpoint deliveries are POSTed to. Must be HTTPS and pass SSRF validation (no private / link-local hosts) — a bad url returns a userError on ["input","url"]. - viewIds (array of strings): content-view (FilterPreset) UUIDs whose new items trigger deliveries. REQUIRED (at least one, each owned by this workspace — resolve ids with getContentViews first) whenever any eventType is view-scoped, which is every subscribable type in v1; omit it only for a non-view event type (none exist yet). Omitting it with a view-scoped type returns a viewIds userError on ["input","viewIds"]. - eventTypes (optional array of strings): event types to subscribe to. Defaults to ["content_view.item_added"] (the only supported type in v1). - metadata (optional object): opaque metadata echoed back in each delivery payload. Max 4KB serialized. Defaults to {}. DUPLICATES: creating an exact duplicate (same url AND same event types AND same viewIds as an existing subscription, in any status) returns a userError on ["input","url"] naming the existing subscription id instead of creating a second subscription. Reusing a url and event types for a DIFFERENT set of viewIds is allowed — those deliver different views. PARTIAL OVERLAP IS NOT BLOCKED: a viewId present in two subscriptions on the same url is delivered once PER subscription, so that url receives the same event twice. Check getWebhookSubscriptions before reusing a url, and prefer editing the existing subscription's viewIds over creating an overlapping one. SIGNING SECRET (store it now — shown ONCE): the response secret (whsec_...) is returned in plaintext ONLY here and from rotateWebhookSubscriptionSecret. It is stored encrypted and NEVER returned again. Relay it to the user immediately and instruct them to save it; there is no way to recover it later (they would have to rotate). TIER LIMITS: the workspace's plan caps the number of subscriptions and views-per-subscription (a violation returns a userError whose message states the limit and current usage). OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status } | null, secret: string | null, userErrors: [{ field, message }] }. webhookSubscription and secret are both null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["input","url"], ["input","viewIds"]). VERIFY AFTER WRITE: confirm the subscription via getWebhookSubscriptions (the WebhookSubscription type never re-exposes the secret). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its o…
deleteCollection
ChatGPTPURPOSE: Delete a Collection (a saved tag set) from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the Collection itself — its tag set and every tag association, so the tag is stripped from every item it was applied to and can no longer be used as a filter. WHAT SURVIVES: the ITEMS ARE NEVER DELETED — they lose only this one tag; every other tag, field, and piece of data on them is untouched. INPUT: - id (required string): the Collection's id, as returned by getCollections / createCollection for this workspace. Resolve ids with getCollections first — NEVER guess a Collection id. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the Collection with getCollection(id:) and CONFIRM WITH THE USER, echoing the Collection's name back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedCollectionId: null — deleting an already-deleted Collection is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). AVAILABILITY: Collection management is available only in workspaces whose Collections use the standard storage. In a workspace that stores Collections in an older format, the ids getCollections returns are not accepted here and the call returns a userError without changing anything. OUTPUT: { deletedCollectionId: string | null, userErrors: [{ field, message }] }. deletedCollectionId echoes the deleted id on success, or is null when the Collection was not found. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal with getCollection(id:) — it returns null once the Collection is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Une…
deleteContentView
ChatGPTPURPOSE: Delete a saved Content View from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears from the workspace's saved views. WHAT SURVIVES: the underlying CONTENT IS NEVER DELETED — the items the view listed remain in the workspace untouched; only this saved filter configuration is removed. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getContentViews / createContentView). Resolve ids with getContentViews first — NEVER guess. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with getContentView(id:) and CONFIRM WITH THE USER, echoing the view's name back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedContentViewId: null — deleting an already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { deletedContentViewId: string | null, userErrors: [{ field, message }] }. deletedContentViewId echoes the deleted id on success, or is null when the view was not found. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal with getContentView(id:) — it returns null once the view is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
deleteCreatorView
ChatGPTPURPOSE: Delete a saved Creator View from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears from the workspace's saved views. WHAT SURVIVES: the underlying CREATORS ARE NEVER DELETED — the creators the view listed remain in the workspace untouched; only this saved filter configuration is removed. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getCreatorViews / createCreatorView). Resolve ids with getCreatorViews first — NEVER guess. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with getCreatorView(id:) and CONFIRM WITH THE USER, echoing the view's name back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedCreatorViewId: null — deleting an already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { deletedCreatorViewId: string | null, userErrors: [{ field, message }] }. deletedCreatorViewId echoes the deleted id on success, or is null when the view was not found. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal with getCreatorView(id:) — it returns null once the view is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
deleteLabel
ChatGPTPURPOSE: Permanently deletes the label from the whole workspace and removes it from every creator that carries it; the creators themselves are kept. This is irreversible and completes in the background — confirm with the user, echoing the label name, before calling. IRREVERSIBLE, workspace-wide. WHAT IS DESTROYED: the label itself, across the whole workspace — it leaves the label taxonomy AND is stripped from every creator that carries it, in every spelling that matches (matching ignores case and surrounding whitespace). WHAT SURVIVES: the CREATORS ARE NEVER DELETED — only the label value is removed from them. Every other label, campaign, collection and view is untouched. INPUT: - labelName (required string): the label to delete, exactly as getLabels returned it. Resolve the spelling with getLabels first — never guess. Not length-limited: a name too long to create may still exist and stays deletable. A blank name is a userError on ["labelName"]. CONFIRM BEFORE CALLING: list the labels with getLabels and CONFIRM WITH THE USER, echoing the label name back to them, before you call this tool. SCHEDULED, NOT IMMEDIATE: deletion is eventual. The label stays in getLabels (it shows deleting: true once the background run has marked it) and may remain in creator customAttributes.labels and in search until the deletion completes. Calling again while a deletion is in flight returns scheduled: true with the same operationId. FAILURES: a name that matches nothing in this workspace is a userError on ["labelName"] with scheduled: false; nothing is scheduled. The not-found check is best-effort: a concurrent delete may still yield scheduled: true. Lock contention or a scheduling outage is an error result, not a userError: retry it, and the retry resumes the same deletion. OUTPUT: { scheduled, operationId, labelName, userErrors: [{ field, message }] }. labelName echoes the name you submitted; operationId is null when userErrors is non-empty. VERIFY AFTER DELETE: poll getLabels until the name is ABSENT; follow progress with getOperation(id: operationId) until COMPLETED — its succeeded/failed/pending counts are always 0 for a label operation; read status, total and processed. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate li…
deleteSocialProfileView
ChatGPTPURPOSE: Delete a saved Social Profile view from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears from the workspace's saved views. WHAT SURVIVES: the underlying SOCIAL PROFILES ARE NEVER DELETED — the profiles the view listed remain in the workspace untouched; only this saved filter configuration is removed. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getSocialProfileViews / createSocialProfileView). Resolve ids with getSocialProfileViews first — NEVER guess. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with getSocialProfileView(id:) and CONFIRM WITH THE USER, echoing the view's name back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedSocialProfileViewId: null — deleting an already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { deletedSocialProfileViewId: string | null, userErrors: [{ field, message }] }. deletedSocialProfileViewId echoes the deleted id on success, or is null when the view was not found. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal with getSocialProfileView(id:) — it returns null once the view is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
deleteViewGroup
ChatGPTPURPOSE: Delete a view group from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the view group itself — the sidebar bucket and its membership rows. It disappears from the workspace's groups. WHAT SURVIVES: the MEMBER VIEWS ARE NEVER DELETED — every Content View and Social Profile View that was in the group becomes ungrouped. Their FilterPreset UUIDs are returned as movedViewIds so you can re-file them into another group if you want. INPUT: - id (required string): the view group's UUID (as returned by getViewGroups / createViewGroup). Resolve ids with getViewGroups first — NEVER guess a group id. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the group with getViewGroup(id:) and CONFIRM WITH THE USER, echoing the group's name back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedViewGroupId: null and movedViewIds: [] — deleting an already-deleted group is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { deletedViewGroupId: string | null, movedViewIds: [string], userErrors: [{ field, message }] }. deletedViewGroupId echoes the deleted id on success, or is null when the group was not found. movedViewIds lists the FilterPreset UUIDs of the views that were ungrouped (empty on not-found or when the group had no members) — invalidate any cached ContentView / SocialProfileView rows in this list. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal with getViewGroup(id:) — it returns null once the group is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns…
deleteWebhookSubscription
ChatGPTPURPOSE: Delete a webhook subscription from the current workspace. This is a HARD, IRREVERSIBLE delete. WHAT IS DESTROYED: the subscription itself AND all of its delivery records (the deliveries cascade). The endpoint stops receiving any further events immediately. WHAT SURVIVES: the content views the subscription pointed at are NEVER deleted — only this subscription (and its delivery history) is removed. INPUT: - id (required string): the subscription's UUID (as returned by getWebhookSubscriptions). Resolve ids with getWebhookSubscriptions first — NEVER guess. CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first look up the subscription and CONFIRM WITH THE USER, echoing the subscription's name/url back to them, before you call this tool. FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a not_found userError (field ["id"]) with deletedWebhookSubscriptionId: null — deleting an already-deleted subscription is a safe no-op (idempotent). Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { deletedWebhookSubscriptionId: string | null, userErrors: [{ field, message }] }. deletedWebhookSubscriptionId echoes the deleted id on success, or is null when the subscription was not found. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER DELETE: confirm removal by calling getWebhookSubscriptions — the id is gone. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
enableWebhookSubscription
ChatGPTPURPOSE: Re-enable a webhook subscription in the current workspace that was AUTO-DISABLED by repeated delivery failures (status DISABLED_BY_FAILURES). Clears the failure counters and, by default, replays the last 24h of terminal FAILED deliveries (nextAttemptAt null) one-shot per row (one more attempt each, lifetime attemptCount preserved, no backoff-ladder reset) — a bounded catch-up, never a retry storm. A FAILED delivery still awaiting its scheduled retry (nextAttemptAt set) is left to the automatic retry, and subscription.ping deliveries are never replayed. A replayed delivery keeps its lastError / lastResponseStatus until the replay settles. INPUT: - id (required string): the failure-disabled subscription's UUID. Resolve it via getWebhookSubscriptions first (look for status DISABLED_BY_FAILURES) — never guess. - replayFailedSince24h (optional boolean, default true): when true, replays each terminal failed delivery from the last 24h once. Pass false to re-enable WITHOUT replaying the backlog. ONLY DISABLED_BY_FAILURES SUBSCRIPTIONS ARE ENABLEABLE: a subscription in any other status (active / disabled_by_user) returns a not_disabled_by_failures userError (field ["id"]) and changes nothing. An already-enabled subscription is no longer DISABLED_BY_FAILURES, so re-calling is a safe no-op not_disabled_by_failures (this is what makes the tool idempotent-safe). To resume a user-paused (DISABLED_BY_USER) subscription, use updateWebhookSubscription with status ACTIVE. FAILURES: an unknown id, or a subscription owned by another workspace, returns a not_found userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak). OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status, consecutiveFailures, lastSuccessAt, disabledAt } | null, userErrors: [{ field, message }] }. On success webhookSubscription.status is ACTIVE and consecutiveFailures is 0. webhookSubscription is null on a not_found / not_disabled_by_failures failure. userErrors[].field is an array of path segments. VERIFY AFTER WRITE: confirm by calling getWebhookSubscriptions (status back to ACTIVE); if you replayed, poll getWebhookDeliveries (filter by subscriptionId) for the catch-up attempts settling. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every…
getCampaign
ChatGPTPURPOSE: Look up a single campaign by id in the current workspace, with its type, externalId, dates and roster size. INPUT: id (required string — the campaign UUID as returned by createCampaign or getCampaigns). OUTPUT: { campaign: { id, name, createdAt, campaignType, externalId, dateStart, dateEnd, creatorsCount } | null }. campaignType is CREATOR_MONITORING or CONTENT_CAPTURE, or null for a campaign of a type the API does not expose (such a campaign cannot be changed with updateCampaign). externalId is null for a campaign created in the Archive app. dateStart / dateEnd are UTC, null when the campaign has none. creatorsCount counts the creators on the roster now; creators added with updateCampaign join it once their background import resolves them. Returns { campaign: null } (NOT an error) when no campaign with that id exists in the current workspace: an unknown id, an id owned by another workspace, or an internal workflow row that getCampaigns does not list either — the same observable, on purpose. Use getCampaigns to list campaigns; createCampaign / updateCampaign to create or change one. Read a campaign's posts with searchItems using filter.campaignsIds. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCampaigns
ChatGPTPURPOSE: List campaigns for the current workspace, newest first. INPUT: optional cursor, limit (default 20, max 100). No filter, no sorting (results are always newest first). OUTPUT: { campaigns: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each campaign node is { id, name, createdAt }. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCollection
ChatGPTPURPOSE: Look up a single Collection (saved tag set) by id in the current workspace. INPUT: id (required string — a Collection id as returned by getCollections / createCollection for this workspace). Ids differ between workspaces depending on how each one stores Collections, so always resolve the id through getCollections rather than constructing or reusing one. OUTPUT: { collection: { id, name, itemCount, group: { id, name } | null } | null }. Returns { collection: null } (NOT an error) when no Collection with that id exists in the current workspace (unknown id, or id owned by another workspace — same observable, on purpose). itemCount is the number of items tagged with the Collection in this workspace. group is the view group the Collection belongs to (set with moveCollectionToGroup), or null when ungrouped — always null where the workspace uses the older Collection storage. In workspaces whose Collections are preset-backed you can pair the returned id with searchItems(presetId:) to fetch the items tagged with the Collection; that pairing does not apply where the workspace uses the older storage. Creating/renaming/deleting a Collection (createCollection / updateCollection / deleteCollection) is available only in workspaces whose Collections use the standard storage — where the workspace uses the older storage those calls return a userError and change nothing. Tagging items with addItemToCollections / removeItemFromCollections works in both. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCollections
ChatGPTPURPOSE: List Collections (saved tag sets) for the current workspace, in the workspace's saved display order. INPUT: none (beyond the workspace selector). OUTPUT: { items: [{ id, name, itemCount, group: { id, name } | null }] } id is the id this workspace accepts for addItemToCollections / removeItemFromCollections and for getCollection(id:). ALWAYS take Collection ids from this tool — never construct them, cache them, or reuse one across workspaces. Some workspaces store Collections in an older format and their ids look different; the ids listed here are always the ones that workspace accepts. itemCount is the number of items in this workspace tagged with the Collection. group is the view group the Collection belongs to (set with moveCollectionToGroup), or null when ungrouped — always null where the workspace uses the older Collection storage. In workspaces whose Collections are preset-backed you can pair a Collection's id with searchItems(presetId:) to fetch the items tagged with that Collection; that pairing does not apply where the workspace uses the older storage. Creating/renaming/deleting a Collection (createCollection / updateCollection / deleteCollection) is available only in workspaces whose Collections use the standard storage — where the workspace uses the older storage those calls return a userError and change nothing. Tagging items with addItemToCollections / removeItemFromCollections works in both. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCompetitorBrand
ChatGPTPURPOSE: Look up a single Competitor Insights brand by id in the current workspace, with aggregate metrics over a period/date window. Also resolves the workspace's own brand by its stable id (isOwnBrand: true). INPUT: id (required string, the brand's Archive id), optional period ("WEEK"|"MONTH", default "MONTH") and date (ISO date, defaults to today). OUTPUT: { id, name, isOwnBrand, earnedMediaValue, engagementsTotal, impressions, reach, postsCount, influencersCount, ownMetricsStatus } on success. For the own entry, ownMetricsStatus is AVAILABLE (null metrics then mean no data in the window) or UNAVAILABLE (retry later); it is null for competitors. Returns an isError tool result with the text "Competitor brand not found" when no brand with that id is tracked in the current workspace (unknown id, or known id tracked only by another workspace — same observable, on purpose). WARNINGS: If the metrics store is temporarily unavailable, brand identity data is still returned with null metrics and a warnings array containing the message below. An own-brand result has ownMetricsStatus "UNAVAILABLE". Back off for minutes before retrying; do not immediately repeat the call. "Competitor metrics are temporarily unavailable. Retry later." RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCompetitorBrandItems
ChatGPTPURPOSE: Paginate posts attributed to a single tracked Competitor Insights brand within a required takenAt time window. INPUT: brandId (required string, the brand's Archive id — not an ISO-8601 date-time); takenAtFrom and takenAtTo (required ISO-8601 date-time, UTC only — offsets other than Z/+00:00/-00:00 are rejected); optional sorting ({ sortKey: TAKEN_AT|EARNED_MEDIA_VALUE, sortOrder: ASC|DESC }, default TAKEN_AT DESC), limit (default 20, max 100), cursor, responseFormat (concise | detailed, default detailed — see OUTPUT). An unknown or foreign brandId returns an empty connection (not an error). EARNED_MEDIA_VALUE ranks posts as one ordering across the whole window. OUTPUT: { items: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. responseFormat "detailed" (default) returns the full per-node shape; "concise" trims each node to id, takenAt, provider, type, originalUrl, caption, socialProfile { id accountName }, and currentEngagement — for listing / filtering / ranking. NOTE: a month's posts become available on day 9 of the following month — the current month is never queryable. The cursor is positional — pass pageInfo.endCursor back with the same brandId, takenAtFrom/takenAtTo, and sorting; responseFormat may change freely between pages. A malformed cursor (including one minted before the ClickHouse cutover) is rejected with "Invalid cursor", and pagination depth is capped at 10,000 posts per query — narrow the takenAt window to go deeper. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCompetitorBrands
ChatGPTPURPOSE: List Competitor Insights brands tracked for the current workspace, newest first, with aggregate metrics over a period/date window, so self-vs-competitor Share of Voice is computable in one call. INPUT: optional cursor, limit (default 20, max 100), period ("WEEK"|"MONTH", default "MONTH"), date (ISO date, defaults to today). No filter, no sorting (results are always newest first). OUTPUT: { brands: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each brand node is { id, name, isOwnBrand, earnedMediaValue, engagementsTotal, impressions, reach, postsCount, influencersCount, ownMetricsStatus }. The workspace's own brand is returned as one additional pinned node on the first page (isOwnBrand: true, name "You"), in addition to up to limit competitor brands; totalCount counts competitor brands only. For the own entry, ownMetricsStatus is AVAILABLE (null metrics then mean no data in the window) or UNAVAILABLE (retry later); it is null for competitors. WARNINGS: If the metrics store is temporarily unavailable, the roster is still returned with null metrics, ownMetricsStatus "UNAVAILABLE", and a warnings array containing the message below. Back off for minutes before retrying; do not immediately repeat the call. "Competitor metrics are temporarily unavailable. Retry later." RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getContentView
ChatGPTPURPOSE: Look up a single saved Content View by id in the current workspace. INPUT: id (required string, the content view's Archive id / FilterPreset UUID). OUTPUT: { contentView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }. Returns { contentView: null } (NOT an error) when no media_deck view with that id exists in the current workspace (unknown id, or id owned by another workspace — same observable, on purpose). Pair the returned id with searchItems(presetId:) to fetch the items belonging to the view. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getContentViews
ChatGPTPURPOSE: List saved Content Views for the current workspace, ordered most-recently-updated first. INPUT: optional groupId (filters to one group; omitted/null returns all Content Views). An unknown or foreign groupId returns an empty list (not an error). OUTPUT: { items: [{ id, name, filters, customAttributeConditions, sort, showReportingStats, group }] } Pair an item's id with searchItems(presetId:) to fetch the items belonging to a view. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCreator
ChatGPTPURPOSE: Look up a single creator (influencer) by id in the current workspace. INPUT: id (required string, the creator's Archive id). OUTPUT: { id, customAttributes } on success. Returns an isError tool result with the text "Creator not found" when no creator with that id exists in the current workspace (unknown id, or known id owned by another workspace — same observable, on purpose). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCreatorView
ChatGPTPURPOSE: Look up a single saved Creator View by id in the current workspace. INPUT: id (required string — the Creator View's Archive id / FilterPreset UUID, as returned by getCreatorViews). OUTPUT: { creatorView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }. Returns { creatorView: null } (NOT an error) when no creator view with that id exists in the current workspace (unknown id, or id owned by another workspace — same observable, on purpose). Pair the returned id with searchCreators(presetId:) to fetch the view's creators. To create/update/delete use createCreatorView / updateCreatorView / deleteCreatorView. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCreatorViews
ChatGPTPURPOSE: List saved Creator Views for the current workspace, ordered most-recently-updated first. INPUT: optional groupId (filters to one group; omitted/null returns all creator views). An unknown or foreign groupId returns an empty list (not an error). OUTPUT: { items: [{ id, name, filters, customAttributeConditions, sort, showReportingStats, group }] } Pair an item's id with searchCreators(presetId:) to fetch the view's creators. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getCustomAttributeSchemas
ChatGPTPURPOSE: List custom attribute schemas for the current workspace, used both to INTERPRET values in the customAttributes JSON field on items/creators AND to BUILD customAttributeConditions filters for searchItems / searchCreators. INPUT: entity (required: "ITEM" or "CREATOR"). ITEM → schemas attached to items (shop_item); CREATOR → schemas attached to creators. OUTPUT: { items: [{ key, name, type, aiGenerated, options: [{ id, name }] }] } - key matches a top-level field inside the entity's customAttributes hash (and is the field in a filter condition). - type is the upper-case attribute type (TEXT, SINGLE_SELECT_V2, MULTIPLE_SELECT_V2, etc.) — pass it as type in a filter condition. - aiGenerated is true when the attribute's value is produced by an AI Filter; searchItems returns the model's explanation for those keys in each item's aiFilterReasons. - options is [] for non-select schemas; for select-type schemas each option's id is what appears as the value (or array element) in customAttributes and is the value you pass when filtering on that field. CUSTOM ATTRIBUTE FILTERS (customAttributeConditions): Filter by the workspace's user-defined custom fields. An array of { field, operator, type, value } objects; multiple entries are AND-ed together. Step 1 - discover fields: call getCustomAttributeSchemas(entity: ITEM | CREATOR). Each schema returns key (use as field), type (use as type), and options: [{ id, name }] for select fields (use an option id as value). Step 2 - build each condition: - field : the schema key (e.g. "sentiment", "links"). - type : the schema type, UPPERCASE - one of TEXT, EMAIL, PHONE, URL, NUMBER, BOOLEAN, DATE, DATETIME, SINGLE_SELECT_V2, SINGLE_SELECT_V3, MULTIPLE_SELECT, MULTIPLE_SELECT_V2, TEXT_LIST, NUMBER_LIST, DATE_LIST, DATETIME_LIST, BOOLEAN_LIST. - operator : UPPERCASE; the valid set depends on the field's type group (see below). - value : depends on type: TEXT / EMAIL / PHONE / URL -> a string. SINGLE_SELECT_V2 / SINGLE_SELECT_V3 -> the chosen option id (UUID). NUMBER -> a number; BETWEEN takes { from, to }. DATE / DATETIME -> ISO-8601 string; BETWEEN takes { from, to }. BOOLEAN -> true / false. MULTIPLE_SELECT / MULTIPLE_SELECT_V2 -> array of at least one option id. TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values. IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null. Operators by type group (an operator outside its group, or a SHIPPING_ADDRESS-typed condition, is rejected with an error naming the field): - Text & single-select (TEXT, EMAIL, PHONE, URL, SINGLE_SELECT_V2, SINGLE_SELECT_V3): IS, IS_NOT, CONTAINS, DOES_NOT_CONTAIN, STARTS_WITH, ENDS_WITH, IS_EMPTY, IS_NOT_EMPTY. (IS / IS_NOT are exact match - for a select field, value is the option id.) - Number (NUMBER): EQUAL, NOT_EQUAL, MORE_THAN, MORE_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, BETWEEN, IS_EMPTY, IS_NOT_EMPTY. - Date (DATE, DATETIME): EQUAL, NOT_EQUAL, MORE_THAN, MORE_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, BETWEEN, IS_EMPTY, IS_NOT_EMPTY, IS_RELATIVE_TO_TODAY. IS_RELATIVE_TO_TODAY value: { relation: "past"|"this"|"next", period: "day"|"week"|"month"|"year" } with an optional numeric offset. - Boolean (BOOLEAN): IS (value true / false). - Multi-value (MULTIPLE_SELECT, MULTIPLE_SELECT_V2, and every *_LIST type): CONTAINS (matches ANY of the values, OR), CONTAINS_ALL (must contain ALL, AND), DOES_NOT_CONTAIN, IS_EMPTY, IS_NOT_EMPTY. NOTE: post_summary and shipping_address are stored but NOT indexed: positive operators match NOTHING, negated ones (IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — read them from each item's customAttributes instead. NOTE: on items, post_date is the post time: DATE/DATETIME with a plain date (whole UTC day), datetime, { from, to } or IS_RELATIVE_TO_TODAY; NOT_EQUAL, IS_EMPTY and "$now" are rejected. labels takes the multi-value operators. A blank field or an empty option list is rejected. Details: README §5.3. Examples: [{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "va…
getEngagementHistory
ChatGPTPURPOSE: Paginated history of engagement metric snapshots for a single item, newest first. INPUT: itemId (required string). Optional cursor, limit (default 20, max 100), and filter { capturedAt: { from, to } } (ISO-8601 date-time bounds, UTC only — offsets other than Z/+00:00/-00:00 are rejected; both bounds are optional). An unknown or foreign itemId returns an isError result with the coded text "Item not found". OUTPUT: { items: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each item node is { at, likes, comments, views, shares, impressions, earnedMediaValue, followers, linearViralityScore }. Metric values in nodes may be null when data has not been captured. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getFilterPresets
ChatGPTPURPOSE: DEPRECATED — prefer the typed view tools getContentViews (accessor MEDIA_DECK) and getCollections (accessor COLLECTIONS); their ids work the same way as a presetId input. This tool still lists saved filter presets for the current workspace (no removal window announced), but new integrations should not adopt it. Use the returned id as presetId: when calling items(...) to scope a search to the preset's saved filter set. INPUT: none (no arguments). OUTPUT: { items: [{ id, name, accessor }, ...] }. accessor is one of "MEDIA_DECK" or "COLLECTIONS" (the resolver only exposes those two; presets with other accessors or visible: false are filtered out). Ordered by the workspace's default group position, then by creation time. Empty workspaces return { items: [] }. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getItemIdsByUrl
ChatGPTPURPOSE: Resolve public social-media post URLs to Item UUIDs in the current workspace. INPUT: urls (required) — an array of 1..100 public HTTP(S) URLs. Any syntactically valid HTTP/HTTPS URL on a publicly routable host is accepted; there is no path allow-list. Recognized shapes (non-exhaustive hint) that can resolve to a stored item: Instagram (/p/, /reel/, /reels/), TikTok (@user/video/{id}, @user/photo/{id}), and YouTube (/shorts/, /watch?v=, youtu.be/). TikTok short links (vm.tiktok.com/…, vt.tiktok.com/…, tiktok.com/t/…) are accepted but not locally resolvable → NOT_FOUND. OUTPUT: { items: [...] } — one row per input URL (duplicates produce duplicate rows). Each row carries url (the original input), itemId (UUID if matched, null otherwise), and status: - FOUND: the URL resolved to a stored item in this workspace. - NOT_FOUND: a valid public URL we cannot resolve (unrecognized shape, short link, or no matching item). - INVALID_URL: a malformed / non-HTTP URL, or a private/reserved host (loopback, RFC1918, link-local, or a name under localhost / .internal / .local / .home / .lan). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getLabels
ChatGPTPURPOSE: List the creator labels of the current workspace, alphabetically by name (case-insensitive). INPUT: none (beyond the workspace selector). OUTPUT: { items: [{ name, deleting }] } name is the label's identity; labels have no id in this API. Pass a name back exactly as returned. Matching ignores case and surrounding whitespace. A label with deleting: true is being removed; do not assign it, and do not recreate it until it disappears from getLabels. Capped at 1000 entries and not paginated. VERIFY-AFTER-WRITE TARGET: call this after any label change in this workspace, including labels that importCreators registers, and confirm the name is present (or gone). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getMediaContents
ChatGPTPURPOSE: Retrieve media contents (images / videos) for shop items OR for tracked Competitor Insights items in the current workspace. INPUT: exactly one of itemIds (Item / shop-item UUIDs) OR competitorBrandItemIds (CompetitorBrandItem UUIDs from getCompetitorBrandItems). Each is an array of 1..100 string IDs. Passing both, or neither, returns an isError result. OUTPUT: { items: [...] } — one node per media-content row. Each node carries id, mediaItemId, type, fileUrl, thumbnailUrl, width, height, deleted; videos additionally carry videoDuration. NOTE: fileUrl and thumbnailUrl are raw CDN URLs (unsigned); YouTube nodes always have fileUrl: null and thumbnailUrl may be null. Unknown, cross-workspace, or non-entitled competitor item IDs yield zero rows (not an error). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getOperation
ChatGPTPURPOSE: Look up a single async operation by id in the current workspace — poll its status and progress (e.g. after refetchEngagementBulk). INPUT: id (required string, the operation's Archive id). OUTPUT: { id, status, operationType, total, processed, succeededCount, failedCount, pendingCount, succeededItemIds, failedItemIds, pendingItemIds, itemIdsTruncated, createdAt, completedAt } on success (completedAt is null until the operation finishes). The three counts are always exact. Each item-id list holds at most 1000 ids: when itemIdsTruncated is true some ids were omitted — use the counts for exact sizes and page through getOperationRecords for the remaining ids; do not report a partial list as the whole outcome. For a creator import (operationType oneschema_creator_import) the three lists are always empty and itemIdsTruncated is false: its records are uploaded rows, not items — page through getOperationRecords for them. Counts and id lists are separate reads: while status is QUEUED or PROCESSING they are a moving snapshot and may briefly disagree as records finish; on a terminal status records no longer change, so the response is exact and self-consistent. status values: QUEUED (not yet started), PROCESSING (in progress), COMPLETED (all items succeeded — terminal), PARTIAL (finished with some failures — terminal), FAILED (all items failed — terminal). Returns an isError tool result with the text "Operation not found" when no operation with that id exists in the current workspace (unknown id, or known id owned by another workspace — same observable, on purpose). ALPHA (creator_import): this is where to poll a creator import (operationType oneschema_creator_import) until its status is terminal. With the creator_import alpha the result also carries failedRecordsCsvUrl and successfulRecordsCsvUrl (download links valid for 1 hour, written shortly after a PARTIAL or FAILED import finishes, null otherwise) and importSource (CSV_UPLOAD or PUBLIC_API). Page through getOperationRecords for each row's handles, creatorId and error. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getOperationRecords
ChatGPTPURPOSE: Page through the per-item records of one operation, newest first — the follow-up to getOperation when itemIdsTruncated is true, and the way to enumerate an operation's items with their per-item status. INPUT: operationId (required string), optional status filter (SUCCEEDED | FAILED | PENDING — PENDING covers not-started, in-progress and retrying records), optional cursor, limit (default 100, max 1000). OUTPUT: { records: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each record is { itemId, status }; itemId is a documented input to getMediaContents / getTranscriptions. totalCount is the exact number of records matching the filter. Returns an isError tool result with the text "Operation not found" when no operation with that id exists in the current workspace, and "Invalid cursor" for a cursor this connection did not issue. ALPHA (creator_import): with the creator_import alpha each record also carries { handles, creatorId, errorCode, errorMessage }, filled for the rows of a creator import (operationType oneschema_creator_import) and null for every other operation type. On those rows itemId is the row key within the import (import_0, ...), not an item id. handles lists the row's [{ provider, handle }] (null for rows stored in a format it cannot read); creatorId is the creator a SUCCEEDED row created or updated, an input to getCreator; errorCode and errorMessage say why a FAILED row failed, the message in words fit to show a person. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getOperations
ChatGPTPURPOSE: List operations for the current workspace, newest first. Only refetch_engagement operation type is listed here; an operation id visible via getOperation may be absent from this list if it is of another internal type. INPUT: optional cursor, limit (default 20, max 100). No filter, no sorting (results are always newest first). OUTPUT: { operations: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each operation node is { id, status, operationType, total, createdAt }. status values: QUEUED (not yet started), PROCESSING (in progress), COMPLETED (all items succeeded — terminal), PARTIAL (finished with some failures — terminal), FAILED (all items failed — terminal). ALPHA (creator_import): with the creator_import alpha the list also holds the workspace's creator imports (operationType oneschema_creator_import); without it they never appear here, though getOperation still returns one by id. Poll an import with getOperation and read its rows with getOperationRecords. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getSocialProfile
ChatGPTPURPOSE: Look up ONE social profile (influencer) in the current workspace. For MANY profiles do NOT loop this per id — call getSocialProfiles once to list/filter a whole page of profiles in a single call (the batch path). INPUT: provide EXACTLY ONE of: - id (the social profile's Archive id), OR - accountName + provider (provider is one of instagram, tiktok, youtube). Optional fallback (default false): when true and the profile is not archived locally, fetch it live from the social platform (slower). Passing both id and accountName/provider, or accountName without provider, or neither, returns an isError result. OUTPUT: { socialProfile: { id, provider, originalUrl, accountName, avatar, private, verified, followers, following, fullName, email, phoneNumbers, proAccount, creator { id } } } on success, or { socialProfile: null } when no profile matches in the current workspace (workspace-scoped lookup; profiles archived only in other workspaces are treated as misses and return null, not an error). With fallback: true a miss may instead return isError: true with one of: - "Upstream social-platform service is temporarily unavailable. Please retry." (UPSTREAM_UNAVAILABLE code in extensions — safe to retry); - a PROVIDER_ERROR message from the platform (e.g. rate-limited); - "User not found on <provider>." (the account does not exist on the platform). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getSocialProfileView
ChatGPTPURPOSE: Look up a single saved Social Profile view by id in the current workspace. INPUT: id (required string — the Social Profile view's Archive id / FilterPreset UUID, as returned by getSocialProfileViews). OUTPUT: { socialProfileView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }. Returns { socialProfileView: null } (NOT an error) when no social-profiles view with that id exists in the current workspace (unknown id, or id owned by another workspace — same observable, on purpose). Pair the returned id with getSocialProfiles(presetId:) to fetch the view's social profiles. To create/update/delete use createSocialProfileView / updateSocialProfileView / deleteSocialProfileView. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getSocialProfileViews
ChatGPTPURPOSE: List saved Social Profile views for the current workspace, ordered most-recently-updated first. INPUT: optional groupId (filters to one group; omitted/null returns all social-profile views). An unknown or foreign groupId returns an empty list (not an error). OUTPUT: { items: [{ id, name, filters, customAttributeConditions, sort, showReportingStats, group }] } Pair an item's id with getSocialProfiles(presetId:) to fetch the view's social profiles. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getSocialProfiles
ChatGPTPURPOSE: List social profiles (Instagram / TikTok / YouTube accounts) tracked for the current workspace, profiles with the most recent content first. This is the batch / multi-profile path — reach for it instead of calling getSocialProfile once per id when you need more than one profile. INPUT (all optional): - filter (object): a SocialProfileFilterInput. Fields: * platform: one of INSTAGRAM, TIKTOK, YOUTUBE, INTERNAL. - presetId (string): a saved Social Profile View id (from getSocialProfileViews / getSocialProfileView). When set, the view's stored customAttributeConditions and sort drive the results and the inline filter argument is IGNORED (the view's filters blob is never applied on read). A non-Social-Profile-View id returns a coded error with extensions.code = "WRONG_VIEW_TYPE"; an unknown or foreign id returns extensions.code = "NOT_FOUND" (cross-workspace ids collapse to NOT_FOUND — no enumeration leak). - cursor (string), limit (integer, default 20, max 100). Cursor note: pass pageInfo.endCursor only — per-node edge cursors are not valid here. - responseFormat (string): "concise" or "detailed" (default) — see OUTPUT. No sorting argument — results are most-recent-content first (a preset's stored sort overrides this). OUTPUT: { socialProfiles: [{ id, provider, originalUrl, accountName, avatar, private, verified, followers, following, fullName, email, phoneNumbers, proAccount, creator { id } }], pageInfo: { hasNextPage, endCursor }, totalCount } responseFormat "detailed" (default) returns the node shape above; "concise" returns { id, accountName, provider, followers, verified } per node — for listing / filtering / ranking. Use "detailed" when contact fields (email, phoneNumbers, fullName) or the full profile shape are needed. NOTE: no inline custom-attribute filter exists on this query — to narrow by custom attributes, create/point at a Social Profile View with customAttributeConditions (createSocialProfileView) and pass its id as presetId. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getTranscriptions
ChatGPTPURPOSE: Fetch transcriptions for the media contents of one or more items in the current workspace. INPUT: itemIds (required) — an array of 1..1000 Item UUIDs. OUTPUT: { items: [...] } — one node per media content that has a non-empty transcript across the requested items. Each node carries mediaContentId and transcript. Items with no (or only empty) transcriptions yield no rows. NOTE: v0 exposes only the itemIds (Elasticsearch) path; the deferred mediaContentIds (DB) path is not available. NOTE: rate-limit cost scales with the number of itemIds but is capped at 100 — a request with more than 100 ids (up to the 1000 max) is charged as 100. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getViewGroup
ChatGPTPURPOSE: Look up a single user-created view group by id in the current workspace, including its member views. INPUT: id (required string — the ViewGroup UUID as returned by getViewGroups / createViewGroup). OUTPUT: { viewGroup: { id, name, contentViews: [{ id, name }], socialProfileViews: [{ id, name }], creatorViews: [{ id, name }], collections: [{ id, name }] } | null }. Member views carry id and name only — fetch a member's configuration (filters, sort, …) with getContentView / getSocialProfileView / getCreatorView / getCollection. contentViews, socialProfileViews, creatorViews, and collections enumerate the group's members in their within-group position order (ties break on id for a stable order). CAMPAIGN Creator Views are never group members (they are campaign-scoped, not workspace-scoped) — workspace Creator Views ARE groupable and appear in creatorViews. Returns { viewGroup: null } (NOT an error) when no group with that id exists in the current workspace (unknown id, or id owned by another workspace — same observable, on purpose). To create/rename/delete a group use createViewGroup / updateViewGroup / deleteViewGroup; to move a view into/out of a group use moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup / moveCollectionToGroup. reorderViewsInGroup rewrites member positions — read the full member list from this tool first, since a reorder that omits members leaves their positions unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getViewGroups
ChatGPTPURPOSE: List user-created view groups for the current workspace, ordered oldest-first. INPUT: none (beyond the workspace selector). OUTPUT: { items: [{ id, name }] } This list view returns group metadata only. To see a group's member views (Content Views + Social Profile Views + Creator Views + Collections, in position order) call getViewGroup(id:). To create/rename/delete a group use createViewGroup / updateViewGroup / deleteViewGroup; to move a view into/out of a group use moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup / moveCollectionToGroup; to reorder members use reorderViewsInGroup (fetch the full member list via getViewGroup first). RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getWebhookDeliveries
ChatGPTPURPOSE: Paginate webhook deliveries (the dead-letter / observability surface) for the current workspace, newest first. THE source for redeliverWebhookDelivery's required deliveryId — filter by status "FAILED" and pick the delivery's id; never guess ids. INPUT: - subscriptionId (optional string): only deliveries for this subscription (resolve via getWebhookSubscriptions). A foreign/unknown id yields an empty page. - status (optional string): PENDING | DELIVERING | SUCCEEDED | FAILED | DROPPED. - limit (optional integer): page size, default 20, max 100. - cursor (optional string): pageInfo.endCursor from the previous page. OUTPUT: { deliveries: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each delivery node is { id, subscriptionId, eventId, status, attemptCount, lastAttemptAt, nextAttemptAt, lastResponseStatus, lastError, responseTimeMs, createdAt, updatedAt }. status values: PENDING (queued/scheduled), DELIVERING (in flight), SUCCEEDED (2xx — terminal), FAILED (retried on the 8-attempt backoff ladder until exhausted; a terminal FAILED — nextAttemptAt null — is redeliverable via redeliverWebhookDelivery), DROPPED (backpressure/rate-limit — terminal, never retried; recover the business event via getWebhookEvents). NOTE: after sendWebhookTestEvent / redeliverWebhookDelivery / enableWebhookSubscription, poll here (filter by subscriptionId) for the attempts settling. RETENTION: delivery history is pruned after 30 days — redeliver within that window. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getWebhookEvents
ChatGPTPURPOSE: Paginate webhook events (the outbox — the Stripe /v1/events pattern) for the current workspace, newest first. The reconciliation surface: query it to recover business events whose deliveries were DROPPED or missed; an event's id doubles as its idempotency key. INPUT: - eventTypes (optional array of strings): only events whose type is in this list. content_view.item_added is the only subscribable type in v1; system types subscription.disabled and subscription.deliveries_dropped also appear in the outbox. - limit (optional integer): page size, default 20, max 100. - cursor (optional string): pageInfo.endCursor from the previous page. OUTPUT: { events: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each event node is { id, eventType, eventVersion, payload, createdAt } — payload is the exact JSON delivered to subscribers. NOTE: cross-reference a delivery's eventId (from getWebhookDeliveries) to its outbox row here. RETENTION: outbox history is pruned after 30 days — reconcile within that window. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getWebhookSubscriptions
ChatGPTPURPOSE: List outbound-webhook subscriptions for the current workspace, newest first. The id-resolution source for updateWebhookSubscription / deleteWebhookSubscription / rotateWebhookSubscriptionSecret / sendWebhookTestEvent / enableWebhookSubscription — resolve subscription ids here, never guess. INPUT: - limit (optional integer): page size, default 20, max 100. - cursor (optional string): nextCursor from the previous page. All subscriptions fit one page today (workspace cap), so paging is only needed once caps rise. - after (optional string, deprecated): alias of cursor kept for existing callers; cursor wins when both are passed. OUTPUT: { items: [{ id, name, url, eventTypes, viewIds, metadata, status, consecutiveFailures, lastSuccessAt, disabledAt, updatedAt }], nextCursor, hasNextPage } status values: ACTIVE (delivering), DISABLED_BY_USER (manual pause — re-enable via updateWebhookSubscription with status ACTIVE), DISABLED_BY_FAILURES (system auto-disable — re-enable via enableWebhookSubscription). consecutiveFailures is the health signal; updatedAt is the rotation-confirmation signal referenced by rotateWebhookSubscriptionSecret. When hasNextPage is true, pass cursor: nextCursor to fetch the next page. NOTE: the signing secret is NEVER returned here (or anywhere after issuance) — it is returned in plaintext exactly once, by createWebhookSubscription / rotateWebhookSubscriptionSecret. To inspect delivery health use getWebhookDeliveries. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getWorkspace
ChatGPTPURPOSE: Get a workspace — its tracked hashtags/mentions/keywords and connected social integrations — in one call. The workspace is selected by the optional workspaceId argument (falling back to the WORKSPACE-ID header, or your sole workspace when you can access exactly one). Call getWorkspaces first to discover the ids you can access. INPUT: optional workspaceId — the workspace UUID (from getWorkspaces) or the numeric workspace id shown in the Archive app. OUTPUT: { workspace: { id, name, hashtags: [...], mentions: [...], keywords: [...], integrations: [...] } }. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
getWorkspaces
ChatGPTPURPOSE: List all workspaces the authenticated caller can access, newest first. INPUT: optional cursor, limit (default 20, max 100). No filter, no sorting (results are always newest first). OUTPUT: { workspaces: [...], pageInfo: { hasNextPage, endCursor }, totalCount }. Each workspace node is { id, name, hashtags, mentions, keywords, integrations }. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
mergeLabels
ChatGPTPURPOSE: Folds one label into another across the whole workspace: every creator carrying labelName ends up carrying intoLabelName, and labelName is deleted. This is irreversible: the two labels' memberships cannot be separated afterwards. The merge completes in the background. AUTOMATION CAVEAT: campaign automations that matched labelName stop matching those creators, and automations that match intoLabelName start matching them. Confirm the merge with the user, echoing both names, before calling. INPUT: - labelName (required string): the label to fold in, exactly as getLabels returned it. Matching ignores case and surrounding whitespace. A blank name is a userError. - intoLabelName (required string): the existing label that absorbs it, exactly as getLabels returned it. A blank name is a userError. RULES: intoLabelName must already exist; a merge never creates a label (use renameLabel to give a label a new name). The same label in any spelling on both sides is refused; use renameLabel to respell a label. FAILURES: refusals are userErrors on ["labelName"], ["intoLabelName"] or ["base"] with scheduled: false, and nothing is scheduled. While a merge of the same labelName is still queued or running, another merge of it is a userError on ["labelName"]: poll getLabels until the first one completes, then retry. Lock contention is an error result, not a userError: retry it. OUTPUT: { scheduled, operationId, userErrors: [{ field, message }] }. operationId is set exactly when scheduled is true. VERIFY AFTER MERGE: poll getLabels until labelName is absent; follow progress with getOperation(id: operationId) — its succeeded/failed/pending counts are always 0 for a label operation; read status, total and processed. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
moveCollectionToGroup
ChatGPTPURPOSE: Move a Collection (a saved tag set) into a view group, or remove it from whatever group it is in. Use this tool for COLLECTIONS ONLY — entities whose ids come from getCollections / getCollection. To move a Content View (ids from getContentViews) use moveContentViewToGroup; to move a Creator View use moveCreatorViewToGroup; to move a Social Profile View use moveSocialProfileViewToGroup. ONE GROUP PER VIEW: a Collection belongs to at most one group at a time. Moving it into a new group IMPLICITLY leaves whatever group it was in before — you do not need to remove it first. INPUT: - collectionId (required string): the Collection's id (from getCollections / getCollection) for this workspace. Resolve ids there first — NEVER guess. - groupId (optional string OR null): the target view group's UUID (from getViewGroups / createViewGroup). OMIT it or pass an explicit null to REMOVE the Collection from any group it is currently in (ungroup). Omitted and explicit null are equivalent here. FAILURES: a collectionId that is unknown, owned by another workspace, or not a Collection (e.g. a Content View or a Creator View) returns a not_found userError on ["collectionId"]. A groupId owned by another workspace returns a not_found userError on ["groupId"]. Same observable for unknown vs cross-workspace, on purpose (no existence leak). AVAILABILITY: Collection management is available only in workspaces whose Collections use the standard storage. In a workspace that stores Collections in an older format, the ids getCollections returns are not accepted here and the call returns a userError without changing anything. OUTPUT: { collection: { id, name, group } | null, userErrors: [{ field, message }] }. On success collection.group reflects the new group (null after an ungroup). collection is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["collectionId"], ["groupId"]). VERIFY AFTER WRITE: confirm the move with getCollection(id:) and inspect its group. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is en…
moveContentViewToGroup
ChatGPTPURPOSE: Move a Content View (a saved item filter) into a view group, or remove it from whatever group it is in. Use this tool for CONTENT VIEWS ONLY — views whose ids come from getContentViews / getContentView. To move a Social Profile View (ids from getSocialProfileViews) use moveSocialProfileViewToGroup instead. ONE GROUP PER VIEW: a view belongs to at most one group at a time. Moving it into a new group IMPLICITLY leaves whatever group it was in before — you do not need to remove it first. INPUT: - viewId (required string): the Content View's FilterPreset UUID (from getContentViews / getContentView). Resolve ids there first — NEVER guess. - groupId (optional string OR null): the target view group's UUID (from getViewGroups / createViewGroup). OMIT it or pass an explicit null to REMOVE the view from any group it is currently in (ungroup). Omitted and explicit null are equivalent here. FAILURES: a viewId that is unknown, owned by another workspace, or not a Content View (e.g. a Social Profile View — use moveSocialProfileViewToGroup — or a Creator View — use moveCreatorViewToGroup) returns a not_found userError on ["viewId"]. A groupId owned by another workspace returns a not_found userError on ["groupId"]. Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { contentView: { id, name, group } | null, userErrors: [{ field, message }] }. On success contentView.group reflects the new group (null after an ungroup). contentView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["viewId"], ["groupId"]). VERIFY AFTER WRITE: confirm the move with getContentView(id:) and inspect its group. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
moveCreatorViewToGroup
ChatGPTPURPOSE: Move a Creator View (a saved crm_creators view) into a view group, or remove it from whatever group it is in. Use this tool for CREATOR VIEWS ONLY — views whose ids come from getCreatorViews / getCreatorView. To move a Content View (ids from getContentViews) use moveContentViewToGroup instead; to move a Social Profile View use moveSocialProfileViewToGroup. ONE GROUP PER VIEW: a view belongs to at most one group at a time. Moving it into a new group IMPLICITLY leaves whatever group it was in before — you do not need to remove it first. INPUT: - viewId (required string): the Creator View's FilterPreset UUID (from getCreatorViews / getCreatorView). Resolve ids there first — NEVER guess. - groupId (optional string OR null): the target view group's UUID (from getViewGroups / createViewGroup). OMIT it or pass an explicit null to REMOVE the view from any group it is currently in (ungroup). Omitted and explicit null are equivalent here. FAILURES: a viewId that is unknown, owned by another workspace, or not a Creator View (e.g. a Content View or a Social Profile View) returns a not_found userError on ["viewId"]. A groupId owned by another workspace returns a not_found userError on ["groupId"]. Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { creatorView: { id, name, group } | null, userErrors: [{ field, message }] }. On success creatorView.group reflects the new group (null after an ungroup). creatorView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["viewId"], ["groupId"]). VERIFY AFTER WRITE: confirm the move with getCreatorView(id:) and inspect its group. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
moveSocialProfileViewToGroup
ChatGPTPURPOSE: Move a Social Profile View (a saved social-profiles view) into a view group, or remove it from whatever group it is in. Use this tool for SOCIAL PROFILE VIEWS ONLY — views whose ids come from getSocialProfileViews / getSocialProfileView. To move a Content View (ids from getContentViews) use moveContentViewToGroup instead. ONE GROUP PER VIEW: a view belongs to at most one group at a time. Moving it into a new group IMPLICITLY leaves whatever group it was in before — you do not need to remove it first. INPUT: - viewId (required string): the Social Profile View's FilterPreset UUID (from getSocialProfileViews / getSocialProfileView). Resolve ids there first — NEVER guess. - groupId (optional string OR null): the target view group's UUID (from getViewGroups / createViewGroup). OMIT it or pass an explicit null to REMOVE the view from any group it is currently in (ungroup). Omitted and explicit null are equivalent here. FAILURES: a viewId that is unknown, owned by another workspace, or not a Social Profile View (e.g. a Content View — use moveContentViewToGroup — or a Creator View — use moveCreatorViewToGroup) returns a not_found userError on ["viewId"]. A groupId owned by another workspace returns a not_found userError on ["groupId"]. Same observable for unknown vs cross-workspace, on purpose (no existence leak). OUTPUT: { socialProfileView: { id, name, group } | null, userErrors: [{ field, message }] }. On success socialProfileView.group reflects the new group (null after an ungroup). socialProfileView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["viewId"], ["groupId"]). VERIFY AFTER WRITE: confirm the move with getSocialProfileView(id:) and inspect its group. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other erro…
redeliverWebhookDelivery
ChatGPTPURPOSE: Manually replay a terminal FAILED webhook delivery in the current workspace — a dead-letter retry. Grants exactly ONE more attempt (CAS-guarded so concurrent replays can't double-enqueue); keeps the lifetime attemptCount / backoff index, and the settlement recorder re-terminates a re-failure. A replay past the retry ceiling is a one-shot, never an automatic retry storm. INPUT: - deliveryId (required string): the failed delivery's UUID (as returned by getWebhookDeliveries, the dead-letter surface). Resolve it with getWebhookDeliveries first — never guess. ONLY TERMINAL failed DELIVERIES ARE REPLAYABLE: status FAILED with a null nextAttemptAt. A FAILED delivery still on the automatic retry schedule (nextAttemptAt set), or a delivery in any other status (pending / delivering / succeeded), returns a not_replayable userError (field ["deliveryId"]) and enqueues nothing. A subscription.ping delivery (from sendWebhookTestEvent) is never replayable — call sendWebhookTestEvent again instead. Re-calling while the replay is still pending is a no-op not_replayable, but NOT IDEMPOTENT overall: once the replay re-fails, another call grants another real attempt — check getWebhookDeliveries before retrying this tool. FAILURES: an unknown id, or a delivery owned by another workspace, returns a not_found userError (field ["deliveryId"]) — same observable on purpose (no existence leak). OUTPUT: { webhookDelivery: { id, subscriptionId, eventId, status, attemptCount, lastAttemptAt, nextAttemptAt, lastResponseStatus, lastError, responseTimeMs, createdAt, updatedAt } | null, userErrors: [{ field, message }] }. On success webhookDelivery.status is pending (re-enqueued). webhookDelivery is null on a not_found / not_replayable failure. userErrors[].field is an array of path segments. VERIFY AFTER WRITE: poll getWebhookDeliveries (filter by subscriptionId) for the delivery's terminal status once the replay settles. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate lim…
refetchEngagementBulk
ChatGPTPURPOSE: Queue an engagement-metrics refresh (likes, comments, shares, views) for up to 1000 items in the current workspace. SPENDS CREDITS: 5 credits per PROCESSABLE item. The upper bound is itemIds.length x 5, but Instagram stories and items already refreshed in the last 24h are SKIPPED and NOT charged, so the ACTUAL spend is processedCount x 5. Credits are reserved when the batch is queued and settled when the operation finishes: credits for items that fail to refresh are released, and the spend on every item that refreshes successfully is IRREVERSIBLE. INPUT: - itemIds (required array of strings, 1-1000 items): Archive item ids to refresh. - confirm (boolean, default false): required true when itemIds exceeds 50 (see below). COST CONFIRMATION (>50 items): a call with more than 50 itemIds requires confirm: true. Without it the tool DOES NOT execute or spend — it returns a cost quote in userErrors (field ["confirm"]) instead. Relay that cost to the user, and only re-call with confirm: true after they approve. A call with 50 or fewer items proceeds without confirm; confirm: true proceeds at any size (still capped at 1000). ASYNC + ANTI-DOUBLE-SPEND: on execution the refresh runs in the background. Results land under operationId — poll getOperation(id: operationId) until its status is terminal (COMPLETED / PARTIAL / FAILED), THEN call getEngagementHistory for the refreshed values. Do NOT expect fresh metrics in this response. NEVER re-issue the same batch while its operation is still running — a duplicate re-spends credits. A 24h dedup window protects immediate duplicates (items refreshed in the last 24h are skipped, not re-charged), but a fresh batch submitted AFTER the window spends again. SKIPPED ITEMS: skippedItemIds lists items that were NOT charged and NOT queued — items already refreshed within the 24h window. Instagram stories are also excluded (silently, not listed). Do NOT "fix" a skip by retrying it: a skip means the data is already fresh (or the item is a story), so retrying only risks a later re-charge once the window elapses. SOURCING itemIds: get item ids from a fresh searchItems call, then pass them here. Item ids from another workspace are silently ignored (they never count toward the charge). OUTPUT: { operationId: string | null, processedCount: integer | null, skippedItemIds: [string] | null, userErrors: [{ field, message }] }. operationId is null when nothing was processable (all filtered / deduped). userErrors[].field is an array of path segments (e.g. ["confirm"], ["itemIds"]), empty when the error concerns the workspace rather than an argument. Common userError cause: insufficient credits (field []). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named be…
removeItemFromCollections
ChatGPTPURPOSE: Remove an item from one or more Collections in the current workspace. A Collection is a workspace-scoped tag set applied to items; removing an item from a Collection strips that tag from the item. This NEVER deletes the item and NEVER deletes the Collection — it only detaches the tag. Removing an item that is not in the Collection is a no-op. INPUT: - itemId (required string): the Archive item id to untag. - collectionIds (array of strings, default []): Collection ids to remove the item from. - collectionNames (array of strings, default []): Collection names to remove the item from, matched case-sensitively within the workspace. You MUST provide at least one of collectionIds or collectionNames. Providing neither returns a userErrors entry ("Provide 'collectionIds' or 'collectionNames'") and makes no change. RESOLVING IDS vs NAMES: prefer ids. Resolve Collection ids with getCollections first, then pass them as collectionIds. Use the ids returned by getCollections for THIS workspace: several id forms are accepted depending on how the workspace stores Collections. An id from another workspace, or one this workspace cannot write through this API, rejects the WHOLE call (all-or-nothing) with a userErrors entry naming the offending id under collectionIds. An id that exists in the workspace but is not writable here reports "exists in this workspace but is not writable through this API" — re-resolve via getCollections and retry with the id it returns. A collectionNames entry that matches no Collection is ignored while another id or name resolves; when nothing resolves the call returns a "No valid collections found" userError (field collectionNames) and makes no change. OUTPUT: { item: { id, customAttributes } | null, userErrors: [{ field, message }] }. item is the item as stored after the write. In workspaces whose Collections use the older storage, item.customAttributes.collections does NOT reflect Collection membership and is returned unchanged — use getCollection(id:).itemCount to confirm the write. In preset-backed workspaces that array holds the item's remaining Collections after the write, as option ids — not the ids getCollections returns; match them by name through getCustomAttributeSchemas (entity ITEM, key "collections"). userErrors[].field is an array of path segments. VERIFY AFTER WRITE: PRIMARY (works in every workspace): getCollection(id: "<collection-id>") — its itemCount reflects the change immediately. Default to this check. SECONDARY (works in every workspace): searchItems(filter: { collectionsIds: ["<collection-id>"] }) with a Collection id from getCollections, and check the item is absent. Search results lag a write by up to about a minute, so this is confirmation, not the immediate check. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. …
renameLabel
ChatGPTPURPOSE: Renames one label across the whole workspace; every creator carrying it follows the new spelling. Nothing is deleted. The rename completes in the background. AUTOMATION CAVEAT: Renaming changes the exact text campaign automations match against (case-sensitive); an automation that matched the old spelling may stop matching. Confirm the rename with the user, echoing both names. INPUT: - labelName (required string): the label to rename, exactly as getLabels returned it. Matching ignores case and surrounding whitespace. Not length-limited. A blank name is a userError. - newLabelName (required string, at most 100 characters): the new spelling, stored as typed. A blank name is a userError. RULES: renaming onto a different label that already exists is refused — there is no implicit merge; mergeLabels folds one label into another. A respelling of the same label (VIP → vip) is allowed. Repeating a completed rename to a different name is refused because the old name no longer exists; repeating a respelling schedules another sweep. FAILURES: refusals are userErrors on ["labelName"], ["newLabelName"] or ["base"] with scheduled: false, and nothing is scheduled. While a rename of the same label is still queued or running, another rename of it is a userError on ["labelName"]: poll getLabels until the first one completes, then retry. Lock contention is an error result, not a userError: retry it. OUTPUT: { scheduled, operationId, userErrors: [{ field, message }] }. operationId is set exactly when scheduled is true. VERIFY AFTER RENAME: poll getLabels until the new spelling is present and the old one absent; follow progress with getOperation(id: operationId) — its succeeded/failed/pending counts are always 0 for a label operation; read status, total and processed. WRITE NOTES (apply to every write tool): - userErrors report domain rejections in a SUCCESSFUL tool result, NOT a crash. Atomicity is tool-specific: valid changes may apply alongside errors. Read each field path and message plus returned state; verify what applied before retrying rejected work. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): idempotentHint: true tools MAY be retried within their documented guarantee. For every OTHER write, READ FIRST with its verify tools, including queued operations, before retrying. NEVER blind-retry non-idempotent create tools, uploadItemFromUrl or refetchEngagementBulk: duplicates create entities/imports or re-spend credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete*, plus mergeLabels, rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: a write costs 50 + 1 per selected payload field unless its description states its own price, charged against the workspace's shared rate-limit bucket; the per-workspace throttle (below) applies. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
reorderViewsInGroup
ChatGPTPURPOSE: Reorder the members (Content Views + Social Profile Views + Creator Views + Collections) inside a view group. The first id in viewIds becomes position 1, the second position 2, and so on. READ THE CURRENT MEMBERS FIRST: call getViewGroup(id:) to fetch the group's full member list (contentViews + socialProfileViews + creatorViews + collections, already in position order), then submit the FULL reordered list. All four member kinds share one position sequence, so a single viewIds list mixes Content View, Social Profile View, Creator View, and Collection ids. ALL-OR-NOTHING: if ANY id in viewIds is not a current member of THIS group (wrong group, wrong workspace, or wrong view kind), or the same id is listed more than once, the entire reorder is REJECTED with a validation_error userError on ["viewIds"] naming the offending ids — and NO positions are changed. Fix the list and retry. OMITTED MEMBERS ARE SAFE: you MAY submit a subset (no error). Listed views get new positions starting at 1; members you leave out are NOT dropped — they keep their relative order and sort AFTER the listed views. To fully control the final order, submit EVERY member (fetch the full list with getViewGroup first). INPUT: - groupId (required string): the view group's UUID (from getViewGroups / createViewGroup). - viewIds (required array, min 1): the member FilterPreset UUIDs in the desired display order. FAILURES: an unknown groupId, or one owned by another workspace, returns a not_found userError on ["groupId"]. Any non-member or repeated id in viewIds returns a validation_error on ["viewIds"] with the offenders named (and rewrites nothing). OUTPUT: { viewGroup: { id, name, contentViews, socialProfileViews, creatorViews, collections } | null, userErrors: [{ field, message }] }. On success viewGroup.contentViews / .socialProfileViews / .creatorViews / .collections ALREADY reflect the new order in the SAME response — no follow-up read is required to verify the ordering. viewGroup is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["groupId"], ["viewIds"]). VERIFY AFTER WRITE: the returned viewGroup already carries the new order in-band, so inspect it directly. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 …
rotateWebhookSubscriptionSecret
ChatGPTPURPOSE: Rotate a webhook subscription's signing secret in the current workspace. Deliveries enter a 24h dual-sign overlap during which they are signed with BOTH the new and previous secret, so the receiver can cut over losslessly. The superseded secret is destroyed and can never be recovered — rotation is IRREVERSIBLE. INPUT: - id (required string): the subscription's UUID (as returned by getWebhookSubscriptions). Resolve it with getWebhookSubscriptions first — never guess. NEW SIGNING SECRET (store it now — shown ONCE): the response secret (whsec_...) is the NEW plaintext secret, returned ONLY here and from createWebhookSubscription. It is stored encrypted and NEVER returned again. Relay it to the user immediately so they can update their receiver within the 24h overlap window; there is no way to recover it later (they would have to rotate again). NOT IDEMPOTENT: every call generates a different secret — do NOT retry blindly. On a timeout, list the subscription to check updatedAt before re-rotating. FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) with a null subscription/secret — same observable on purpose (no existence leak). OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status, lastSuccessAt, disabledAt } | null, secret: string | null, userErrors: [{ field, message }] }. webhookSubscription and secret are both null on a not-found / userErrors failure. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY AFTER WRITE: confirm the rotation by calling getWebhookSubscriptions (the WebhookSubscription type never re-exposes the secret; use updatedAt as the signal). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool exec…
searchCreators
ChatGPTPURPOSE: Search creators (influencers) tracked for the current workspace, newest first. INPUT (all optional): - filter (object): a CreatorFilterInput. Fields: campaignsIds: array of campaign ids (from getCampaigns) — the creators on those campaigns' rosters. Multiple ids OR together; an unknown or foreign id matches nothing. locations: array of { country, region, city } canonical exact-match objects. Components AND within an entry; multiple entries OR together (e.g. to disambiguate Portland, Oregon vs Portland, Maine). Canonical names come from the Geo pipeline. - customAttributeConditions (array): filter by user-defined creator fields — see the CUSTOM ATTRIBUTE FILTERS section below (call getCustomAttributeSchemas with entity CREATOR to discover them). - presetId (string): a saved Creator View id — a workspace Creator View (from getCreatorViews / getCreatorView) or a campaign Creator View (from getCampaignCreatorViews / getCampaignCreatorView; those management tools are ALPHA-gated, but the id itself works here without any opt-in). When set, the view's stored custom-attribute conditions and sort drive the search and the filter / customAttributeConditions arguments are IGNORED (the view's filters blob is never applied). A non-Creator-View id returns a coded error with extensions.code = "WRONG_VIEW_TYPE"; an unknown or foreign id returns extensions.code = "NOT_FOUND". - cursor (string), limit (integer, default 20, max 100). - includeTotalCount (boolean, default false): include totalCount — the exact number of matches across the whole result set. The exact count is the expensive part of the search on large workspaces and adds 5 to the call cost; paging only needs pageInfo.hasNextPage, so leave this off unless the total itself is the answer. No sorting argument — results are newest first (a preset's stored sort overrides this). OUTPUT: { creators: [...], pageInfo: { hasNextPage, endCursor } } — plus totalCount when includeTotalCount is true. CAMPAIGN ROSTER / NON-POSTERS: searchCreators(filter: { campaignsIds: [id] }) returns campaign X's creator roster. Pair it with searchItems(filter: { campaignsIds: [id] }) and difference the two sets on creator identity: each roster creator's own top-level id from searchCreators against each item's creator.id from searchItems. Campaign membership is per creator, and every item carries the creator.id of the creator who posted it, so a roster creator whose id is no item's creator.id has not posted for that campaign yet. searchItems returns creator.id in BOTH responseFormat modes, so "concise" is enough for this diff. Do NOT match on handles — neither the roster's socialProfiles[].accountName nor an item's socialProfile.accountName is a valid key here. A workspace can hold two creator records with the same handle (commonly one per platform, never merged), so a handle match counts a creator as a poster when only their same-handle sibling posted, under-reporting non-posters. Page BOTH calls to the end (follow pageInfo.endCursor while hasNextPage) before differencing them; comparing first pages reports creators as non-posters when their posts are merely on a later page. CUSTOM ATTRIBUTE FILTERS (customAttributeConditions): Filter by the workspace's user-defined custom fields. An array of { field, operator, type, value } objects; multiple entries are AND-ed together. Step 1 - discover fields: call getCustomAttributeSchemas(entity: ITEM | CREATOR). Each schema returns key (use as field), type (use as type), and options: [{ id, name }] for select fields (use an option id as value). Step 2 - build each condition: - field : the schema key (e.g. "sentiment", "links"). - type : the schema type, UPPERCASE - one of TEXT, EMAIL, PHONE, URL, NUMBER, BOOLEAN, DATE, DATETIME, SINGLE_SELECT_V2, SINGLE_SELECT_V3, MULTIPLE_SELECT, MULTIPLE_SELECT_V2, TEXT_LIST, NUMBER_LIST, DATE_LIST, DATETIME_LIST, BOOLEAN_LIST. - operator : UPPERCASE; the valid set depends on the field's type group (see below). - value : depends on type: TEXT / EMAIL / PHONE / URL -> a string. SINGLE_SELECT_V2…
searchItems
ChatGPTPURPOSE: Search archived social content (Instagram, TikTok, YouTube) for the current workspace. INPUT (all optional unless noted): - filter (object): an ItemFilterInput. Field names are camelCase and all enum VALUES are UPPERCASE. Available fields: provider: INSTAGRAM | TIKTOK | YOUTUBE | INTERNAL (single value). itemTypes: array of POST, REEL, STORY (Instagram), TIKTOK, TIKTOK_STORY (TikTok), YOUTUBE, YOUTUBE_SHORT (YouTube). NOTE: an itemType encodes BOTH platform and format, so a TikTok post is "TIKTOK" (not "POST"); combining provider:TIKTOK with itemTypes:["POST"] matches nothing. Use provider OR itemTypes, not a conflicting pair. contentTypes: array of IMAGE | VIDEO. takenAt: { from, to } ISO-8601 date-time (publication-date range). engagement: array of { field, range: { from, to } }; field one of LIKE_COUNT, COMMENT_COUNT, MERGED_VIEW_PLAY_COUNT, SHARE_COUNT, ENGAGEMENTS_RATE, IMPRESSIONS_RATE, EARNED_MEDIA_VALUE. ENGAGEMENTS_RATE and IMPRESSIONS_RATE bounds are whole percent (5 = 5%); values below 1% are not expressible. viralityScore: array of HIGH | MEDIUM | LOW | NOT_VIRAL. followersCount: { from, to } (author follower-count range). verified: array of true / false (author verification). socialProfileAccountTypes: array of PERSONAL | CREATOR | BUSINESS (Instagram account typing). accountNames: array of handles. socialProfileIds: array of IDs. tagsNames: array of hashtag/mention names. sourcesIds: array of source IDs. campaignsIds / collectionsIds / shopifyProductsIds / ids: arrays of IDs. creatorLocations / ugcLocations: array of { country, region, city } canonical exact-match objects (components AND within an entry; entries OR together). importType: MANUAL | AUTOMATIC. tiktokSparkCodeStatus / instagramWhitelistingStatus / usageRightsStatus: arrays of APPROVED | EXPIRED | IN_QUEUE | REJECTED | REQUESTED. tiktokWhitelistingEligibility: ALL (items by TikTok creators reachable for whitelisting). * superSearch: { mode, searchQuery, imageUrl, similarMediaContentId, fileName } where mode is EMBEDDING_CONTENT | FUZZY_CAPTION | FUZZY_TRANSCRIPTION. NOTE: saved content-view filters (from getContentViews) use a lowercase representation (e.g. "like_count"); translate field names to camelCase and values to UPPERCASE here. - sorting (array): [{ sortKey, sortOrder }]. sortKey one of TAKEN_AT, MERGED_VIEW_PLAY_COUNT, LIKE_COUNT, COMMENT_COUNT, SHARE_COUNT, EARNED_MEDIA_VALUE, LINEAR_VIRALITY, EXPONENTIAL_VIRALITY, ACCOUNT_NAME, FOLLOWERS_COUNT; sortOrder ASC | DESC. Defaults to TAKEN_AT DESC. - customAttributeConditions (array): filter by user-defined fields — see the CUSTOM ATTRIBUTE FILTERS section below. - presetId (string): a saved-view id — get one from getContentViews (content views) or getCollections (collections); the legacy getFilterPresets tool (DEPRECATED) returns the same ids. Accessor must be MEDIA_DECK or COLLECTIONS. When set, the preset's stored filter, sorting, and custom-attribute conditions drive the search and the filter / sorting / customAttributeConditions arguments are IGNORED. A not-found or foreign preset id returns the same error with extensions.code = "WRONG_VIEW_TYPE_FOR_ITEMS" as a wrong-accessor preset. A preset whose stored conditions fail select-type validation silently falls back to the inline filter / sorting / customAttributeConditions arguments instead of erroring. - cursor (string), limit (integer, default 20, max 100). - includeTotalCount (boolean, default false): include totalCount — the exact number of matches across the whole result set. The exact count is the expensive part of the search on large workspaces and adds 5 to the call cost; paging only needs pageInfo.hasNextPage, so leave this off unless the total itself is the answer. - responseFormat (string): "concise" or "detailed" (default) — see OUTPUT. OUTPUT: { items: [...], pageInfo: { hasNextPage, endCursor } } — plus totalCount when includeTotalCount is true. responseFormat "detailed" (default) returns the full per-node shape, including customAttributes and aiFilterReasons — the AI Filter's per-ke…
sendWebhookTestEvent
ChatGPTPURPOSE: Send a signed synthetic ping to a webhook subscription's endpoint and return the SETTLED delivery, so you can confirm the endpoint is reachable and verifies signatures. The ping payload is built SERVER-SIDE — it is never caller-supplied. SIDE EFFECT: this performs a REAL outbound HTTPS POST to the subscription's configured url. It spends no credits, but it does hit the live endpoint — do not spam it. INPUT: - id (required string): the subscription's UUID (as returned by getWebhookSubscriptions). Resolve it with getWebhookSubscriptions first — never guess. NOT IDEMPOTENT: each call is a fresh delivery attempt against the endpoint. The result is SYNCHRONOUS — the returned delivery is already settled (its status / lastResponseStatus / responseTimeMs reflect the ping's outcome). PING SEMANTICS: the ping's event type is subscription.ping (envelope, X-Archive-Event-Type header and getWebhookEvents). It is a single attempt — a failed ping is never retried and never counts toward consecutiveFailures or auto-disable. Paused and auto-disabled subscriptions can be pinged, so you can check an endpoint before resuming it. A ping skips the host circuit breaker: it is attempted even while the endpoint's host is paused after repeated failures, and it never takes the single probe attempt a paused host allows. FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) with a null delivery — same observable on purpose (no existence leak). RATE LIMIT: test events are throttled PER SUBSCRIPTION (a short cooldown between pings). Over the limit you get a userError (field ["id"]) "Rate limit exceeded. Retry after N seconds." with a null delivery — wait the stated seconds and retry; do not spam. OUTPUT: { webhookDelivery: { id, subscriptionId, eventId, status, attemptCount, lastAttemptAt, nextAttemptAt, lastResponseStatus, lastError, responseTimeMs, createdAt, updatedAt } | null, userErrors: [{ field, message }] }. webhookDelivery is null on a not-found failure. Read status / lastResponseStatus / lastError to see whether the endpoint accepted the ping. userErrors[].field is an array of path segments (e.g. ["id"]). VERIFY: the returned delivery is already settled — no follow-up read needed. To inspect later, call getWebhookDeliveries filtered by subscriptionId. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, …
updateCollection
ChatGPTPURPOSE: Rename a Collection (a saved tag set) in the current workspace. Rename is the ONLY thing this changes — a Collection's tagged items are untouched. INPUT: - id (required string): the Collection's id, as returned by getCollections / createCollection for this workspace. Resolve ids with getCollections first — NEVER guess a Collection id. - name (required string): the new name. Names are UNIQUE within the workspace. FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable, on purpose (no cross-workspace existence leak). Renaming to a name another Collection already uses returns a duplicate-name userError (field ["input","name"]) and changes NOTHING; renaming to the Collection's current name is an allowed no-op. AVAILABILITY: Collection management is available only in workspaces whose Collections use the standard storage. In a workspace that stores Collections in an older format, the ids getCollections returns are not accepted here and the call returns a userError without changing anything. OUTPUT: { collection: { id, name, itemCount } | null, userErrors: [{ field, message }] }. collection is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["id"], ["input","name"]). VERIFY AFTER WRITE: confirm the rename with getCollection(id:). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
updateContentView
ChatGPTPURPOSE: Partial-update a saved Content View in the current workspace. Only the fields you supply are changed; omitted fields are left untouched. COMPOSE WITH filter / sorting (typed, as in searchItems); send a get `filters` verbatim only to copy another view — see WHOLESALE REPLACE below. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getContentViews / createContentView). Resolve ids with getContentViews first — never guess. - name (optional string): when provided, replaces the name. - filter (optional object): typed filter (searchItems `filter`); REPLACES the filter blob. At most one of filter / filters. - filters (optional object): when provided, REPLACES the entire filter blob (see WHOLESALE REPLACE + VIEW FILTERS below). - customAttributeConditions (optional array): when provided, replaces the whole list; [] clears all conditions. - sorting (optional array): typed sort (searchItems `sorting`); replaces the sort. At most one of sorting / sort. - sort (optional array): when provided, replaces the sort directives wholesale. - showReportingStats (optional boolean): when provided, replaces the flag. WHOLESALE REPLACE (important): `filter` / `filters`, `sorting` / `sort`, and `customAttributeConditions` are NOT merged element-by-element — sending one REPLACES the stored value entirely. To tweak one filter key, send the FULL desired blob (existing keys you still want + your change), not just the delta. To keep a field unchanged, OMIT it (do not send null — an omitted field is unchanged; there is no "clear filters to null" — pass {} to clear filters to empty). FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak). OUTPUT: { contentView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. `contentView` is null on a userErrors failure. `userErrors[].field` is an array of path segments (e.g. ["id"], ["input","customAttributeConditions"]). VERIFY AFTER WRITE: confirm the update with getContentView(id:). VIEW FILTERS (the `filters` blob): `filters` is a JSON object mapping filter keys to values, stored verbatim. Pass {} for "no filters". Unknown keys pass through. Enum-backed keys take the same enum names as searchItems `filter`. Stored values (such as "tiktok" or "Approved") are also accepted, in any letter case. An invalid value is rejected with a userError. To copy an existing view, clone its `filters` verbatim (fetch it with the matching get tool). createContentView / updateContentView also take a typed filter (the same object searchItems filter takes) and sorting; PREFER them when composing a content view, and use filters / sort only to clone a get* output verbatim. CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types: array of FilterItemType names: POST, REEL, STORY, TIKTOK, TIKTOK_STORY, YOUTUBE, YOUTUBE_SHORT. - provider: one Provider name: INSTAGRAM, TIKTOK, INTERNAL, YOUTUBE. - content_types: array of MediaContentType names: IMAGE, VIDEO. - virality_score: array of FilterViralityScore names: HIGH, MEDIUM, LOW, NOT_VIRAL. - import_type: one FilterImportType name: MANUAL, AUTOMATIC. - social_profile_account_types: array of SocialProfileAccountType names: PERSONAL, CREATOR, BUSINESS. - usage_rights_status, instagram_whitelisting_status, tiktok_spark_code_status: each an array of FilterContractStatus names: APPROVED, EXPIRED, IN_QUEUE, REJECTED, REQUESTED. - tiktok_whitelisting_eligibility: one FilterTikTokWhitelistingEligibility name: ALL. - engagement: array of range entries whose "field" is a FilterEngagementField name: LIKE_COUNT, COMMENT_COUNT, MERGED_VIEW_PLAY_COUNT, SHARE_COUNT, ENGAGEMENTS_RATE, IMPRESSIONS_RATE, EARNED_MEDIA_VALUE. - super_search: object whose "mode" is a FilterSuperSearchMode name: EMBEDDING_CONTENT, FUZZY_TRANSCRIPTION, FUZZY_CAPTION. - taken_at: publication…
updateCreatorView
ChatGPTPURPOSE: Partial-update a saved Creator View in the current workspace. Only the fields you supply are changed; omitted fields are left untouched. CLONE FIRST when changing filters: fetch the view (getCreatorView(id:)) or a similar one, copy its filters, adapt it, and send the WHOLE desired blob — see WHOLESALE REPLACE below. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getCreatorViews / createCreatorView). Resolve ids with getCreatorViews first — never guess. - name (optional string): when provided, replaces the name. - filters (optional object): when provided, REPLACES the entire filter blob (see WHOLESALE REPLACE + VIEW FILTERS below). The blob is stored and echoed back but NOT applied on read — creators(presetId:) narrows ONLY by customAttributeConditions + sort. - customAttributeConditions (optional array): when provided, replaces the whole list; [] clears all conditions. - sort (optional array): when provided, replaces the sort directives wholesale; field must be created_at, full_name or id (id always ascending), else a userError on ["input","sort"]. - showReportingStats (optional boolean): when provided, replaces the flag. WHOLESALE REPLACE (important): filters, sort, and customAttributeConditions are NOT merged element-by-element — sending one REPLACES the stored value entirely. To tweak one filter key, send the FULL desired blob (existing keys you still want + your change), not just the delta. To keep a field unchanged, OMIT it (do not send null — an omitted field is unchanged; there is no "clear filters to null" — pass {} to clear filters to empty). FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak). OUTPUT: { creatorView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. creatorView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["id"], ["input","customAttributeConditions"]). VERIFY AFTER WRITE: confirm the update with getCreatorView(id:). VIEW FILTERS (the filters blob): filters is a JSON object mapping filter keys to values, stored verbatim. Pass {} for "no filters". Unknown keys pass through. Enum-backed keys take the same enum names as searchItems filter. Stored values (such as "tiktok" or "Approved") are also accepted, in any letter case. An invalid value is rejected with a userError. To copy an existing view, clone its filters verbatim (fetch it with the matching get tool). createContentView / updateContentView also take a typed `filter` (the same object searchItems `filter` takes) and `sorting`; PREFER them when composing a content view, and use `filters` / `sort` only to clone a get output verbatim. CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types: array of FilterItemType names: POST, REEL, STORY, TIKTOK, TIKTOK_STORY, YOUTUBE, YOUTUBE_SHORT. - provider: one Provider name: INSTAGRAM, TIKTOK, INTERNAL, YOUTUBE. - content_types: array of MediaContentType names: IMAGE, VIDEO. - virality_score: array of FilterViralityScore names: HIGH, MEDIUM, LOW, NOT_VIRAL. - import_type: one FilterImportType name: MANUAL, AUTOMATIC. - social_profile_account_types: array of SocialProfileAccountType names: PERSONAL, CREATOR, BUSINESS. - usage_rights_status, instagram_whitelisting_status, tiktok_spark_code_status: each an array of FilterContractStatus names: APPROVED, EXPIRED, IN_QUEUE, REJECTED, REQUESTED. - tiktok_whitelisting_eligibility: one FilterTikTokWhitelistingEligibility name: ALL. - engagement: array of range entries whose "field" is a FilterEngagementField name: LIKE_COUNT, COMMENT_COUNT, MERGED_VIEW_PLAY_COUNT, SHARE_COUNT, ENGAGEMENTS_RATE, IMPRESSIONS_RATE, EARNED_MEDIA_VALUE. - super_search: object whose "mode" is a FilterSuperSearchMode name: EMBEDDING_CONTENT, FUZZY_TRANSCRIPTION, FUZZY_CAPTION. - tak…
updateSocialProfileView
ChatGPTPURPOSE: Partial-update a saved Social Profile view in the current workspace. Only the fields you supply are changed; omitted fields are left untouched. CLONE FIRST when changing filters: fetch the view (getSocialProfileView(id:)) or a similar one, copy its filters, adapt it, and send the WHOLE desired blob — see WHOLESALE REPLACE below. INPUT: - id (required string): the view's canonical FilterPreset UUID (as returned by getSocialProfileViews / createSocialProfileView). Resolve ids with getSocialProfileViews first — never guess. - name (optional string): when provided, replaces the name. - filters (optional object): when provided, REPLACES the entire filter blob (see WHOLESALE REPLACE + VIEW FILTERS below). - customAttributeConditions (optional array): when provided, replaces the whole list; [] clears all conditions. - sort (optional array): when provided, replaces the sort directives wholesale. - showReportingStats (optional boolean): when provided, replaces the flag. WHOLESALE REPLACE (important): filters, sort, and customAttributeConditions are NOT merged element-by-element — sending one REPLACES the stored value entirely. To tweak one filter key, send the FULL desired blob (existing keys you still want + your change), not just the delta. To keep a field unchanged, OMIT it (do not send null — an omitted field is unchanged; there is no "clear filters to null" — pass {} to clear filters to empty). FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak). OUTPUT: { socialProfileView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null, userErrors: [{ field, message }] }. socialProfileView is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["id"], ["input","customAttributeConditions"]). VERIFY AFTER WRITE: confirm the update with getSocialProfileView(id:). VIEW FILTERS (the filters blob): filters is a JSON object mapping filter keys to values, stored verbatim. It is OPAQUE and unvalidated on write — an invalid blob is accepted but may render the view broken in the app. STRONGLY PREFER cloning an existing view's filters (fetch it with the matching get tool) over composing from scratch. Pass {} for "no filters". CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read via items(presetId:) / searchItems(presetId:): - item_types : array of item types, e.g. ["POST","REEL","STORY","SHORT"]. - provider : one of "INSTAGRAM","TIKTOK","YOUTUBE". - content_types : array of media types, e.g. ["IMAGE","VIDEO"]. - taken_at : publication-date range { "from": ISO8601, "to": ISO8601 }. - virality_score : array of "VIRAL","HIGH","MEDIUM","LOW". - tags_names : array of hashtag / mention tag names. - campaigns_ids : array of campaign ids. - collections_ids : array of Collection ids (as returned by getCollections). - social_profile_ids : array of social profile ids. - account_names : array of social profile handles. - followers_count : follower-count range { "from": int, "to": int }. SOCIAL-PROFILE-VIEW `filters` (createSocialProfileView / updateSocialProfileView): the blob is stored and echoed back by the get tools, but it is NOT applied when reading profiles — socialProfiles(presetId:) / getSocialProfiles(presetId:) filter ONLY by the view's customAttributeConditions and sort. To make a Social Profile View that actually narrows results, use customAttributeConditions (see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters unless cloning an existing view verbatim. CREATOR-VIEW filters (createCreatorView / updateCreatorView): same caveat — the blob is stored and echoed back by the get* tools, but it is NOT applied when reading creators. creators(presetId:) / searchCreators(presetId:) narrow ONLY by the view's customAttributeConditions and sort. To make a Creator View that actually narrows results, use customAttributeConditions (see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters unless clo…
updateViewGroup
ChatGPTPURPOSE: Rename a view group in the current workspace. Rename is the ONLY thing this changes — the group's member Content Views / Social Profile Views / Creator Views and their order are untouched. To change membership use moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup; to reorder use reorderViewsInGroup. INPUT: - id (required string): the view group's UUID (as returned by getViewGroups / createViewGroup). Resolve ids with getViewGroups first — NEVER guess a group id. - name (required string): the new name. Names are UNIQUE within the workspace. FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable, on purpose (no cross-workspace existence leak). Renaming to a name another group already uses returns a validation_error userError (field ["input","name"]) and changes NOTHING. OUTPUT: { viewGroup: { id, name, contentViews, socialProfileViews, creatorViews, collections } | null, userErrors: [{ field, message }] }. viewGroup is null on a userErrors failure. userErrors[].field is an array of path segments (e.g. ["id"], ["input","name"]). VERIFY AFTER WRITE: confirm the rename with getViewGroup(id:). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument validation: arguments failing schema validation return isError text like "Invalid argument '$.<path>': <reason>". - Unexpected failures: any other error returns isError text "Tool execution failed".
updateWebhookSubscription
ChatGPTPURPOSE: Partial-update a webhook subscription in the current workspace. Only the fields you supply are changed; omitted fields are left untouched. Pause or resume the subscription via status. INPUT: - id (required string): the subscription's UUID. Resolve it via getWebhookSubscriptions first — never guess. - name (optional string): when provided, replaces the name. - url (optional string): when provided, re-validated (HTTPS + SSRF) and replaces the endpoint. - viewIds (optional array of strings): when provided, REPLACES the whole list (re-checked for workspace ownership and the tier cap). Required while any subscribed eventType is view-scoped (every v1 type); must be empty for a non-view type. Changing eventTypes re-checks this against the resulting combination. - eventTypes (optional array of strings): when provided, replaces the list. - metadata (optional object): when provided, replaces it (max 4KB serialized). - status (optional string): ACTIVE to resume, DISABLED_BY_USER to pause. DISABLED_BY_FAILURES is system-owned and rejected (userError on ["input","status"]). WHOLESALE REPLACE: viewIds / eventTypes / metadata are NOT merged — sending one REPLACES the stored value entirely. To keep a field unchanged, OMIT it. FAILURES: an unknown id, or an id owned by another workspace, returns a not_found userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak). NOTE: the signing secret is never changed here — use rotateWebhookSubscriptionSecret for that. OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status, lastSuccessAt, disabledAt } | null, userErrors: [{ field, message }] }. webhookSubscription is null on a userErrors / not-found failure. userErrors[].field is an array of path segments (e.g. ["id"], ["input","url"]). VERIFY AFTER WRITE: confirm the update by calling getWebhookSubscriptions. WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or trigger a real delivery attempt — they are not purely additive. Say what will change before you call one. Being destructive does NOT on its own require a confirmation round-trip; only the IRREVERSIBLE tools named below do. - IRREVERSIBLE tools — every delete, plus rotateWebhookSubscriptionSecret (the previous signing secret is gone for good) and refetchEngagementBulk (credits for refreshed items are not refunded): CONFIRM WITH THE USER first, echoing the target entity's name/id back to them, and state that the effect is IRREVERSIBLE, before you call. No other write tool needs a confirmation round-trip. - Cost: every write costs 50 + 1 per selected payload field, charged against the workspace's shared rate-limit bucket; the existing per-workspace throttle semantics (below) apply unchanged. RUNTIME NOTES: - Rate limiting: a weighted per-workspace rate limit is enforced on every call. A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds." - Argument va…
uploadItemFromUrl
ChatGPTPURPOSE: Enqueue a single public social-media post URL (Instagram, TikTok, or YouTube) for import into the current workspace. Once ingested, the post becomes a normal Archive item. INPUT: - url (required string): the public post URL to import. ASYNC — success: true MEANS ENQUEUED, NOT IMPORTED. The import runs asynchronously in the background (SocialBridge); it does NOT complete during this call. success: true only means the URL was accepted and the import was queued. The item typically appears a few minutes later — do NOT expect it in the response, and do NOT assume it exists yet. POLLING / VERIFY AFTER WRITE: to find the imported item, wait, then call getItemIdsByUrl with the SAME url: 1. On success: true, wait (start ~1 minute, then back off). 2. Call getItemIdsByUrl(urls: ["<the same url>"]). 3. An empty result / status NOT_FOUND means the import is still ingesting (or failed) — keep waiting and polling. Item id(s) present (status FOUND) means the import is done. DO NOT RE-SUBMIT WHILE WAITING. There is no de-duplication: each uploadItemFromUrl call enqueues a SEPARATE import (a second ShopItemImport row and a second background job) for the same URL. Re-submitting to "hurry it along" duplicates work — poll getItemIdsByUrl instead. ACCEPTED URLS: this tool does NOT validate platform-specific link shapes — any syntactically valid HTTP/HTTPS URL on a publicly routable host is accepted at enqueue time and passed through to ingestion as-is. Submit the post link exactly as you have it; there is no path allow-list. This covers every TikTok form — video permalinks (@user/video/{id}), photo-carousel permalinks (@user/photo/{id}), and short links (vm.tiktok.com/…, vt.tiktok.com/…, tiktok.com/t/…) — plus Instagram (/p/, /reel/) and YouTube (/shorts/, /watch?v=, youtu.be/). The one host restriction: a link pointing at a loopback, private, link-local or otherwise reserved host (an RFC1918 / 127.0.0.0/8 / 169.254.0.0/16 IP literal, or a name under localhost / .internal / .local / .home / .lan) is rejected synchronously. Whether a given URL resolves to an importable post (public vs private, supported platform, live vs deleted) is determined asynchronously during ingest — an accepted URL that never resolves never appears in getItemIdsByUrl. COMMON userError CAUSES (returned synchronously, success: false): - a malformed / non-HTTP URL ("Invalid URL — must be a valid HTTP or HTTPS URL.", field ["input", "url"]); - a non-public host ("Invalid URL — the host is not publicly routable.", field ["input", "url"]); - the workspace has no user to attribute the import to (field ["shop"]); - the background enqueue failed (field ["input", "url"], "Failed to enqueue …"). OUTPUT: { success: boolean | null, userErrors: [{ field, message }] }. userErrors[].field is an array of path segments into the uploadItemFromUrl mutation's arguments (e.g. ["input", "url"]). WRITE NOTES (apply to every write tool): - userErrors mean the write DID NOT happen: a non-empty userErrors array is a SUCCESSFUL tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read each entry's field (an array of path segments) and message, correct the input, and either retry or ask the user — do not treat it as a transport error. - Verify after write: after a success, confirm the change with the READ tool this tool's own description names (e.g. a get/search tool) before reporting success to the user. Deletes are confirmed when that read returns null. - Retry discipline on a timeout / transport error (you cannot tell if the write landed): tools annotated idempotentHint: true MAY be retried directly WITHOUT a verification read — a duplicate apply converges to the same state. For every OTHER (non-idempotent) write, READ FIRST with the verify tool above to check whether it already took effect BEFORE retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a duplicate creates a second entity, a second import, or re-spends credits. - Destructive tools (destructiveHint: true) CHANGE OR REMOVE data that already exist…