Back to tracker
Plugin tracker
Tools
Explore what tracked Claude Connectors and ChatGPT Plugins can actually do. Search by tool, Plugin, Brand, category, verb, or access requirement.
Latest snapshot2026-09-13USmethodology registry-public-v1
Searchable tools
119,491
Authless tools
7,773
Auth required
107,252
Described tools
64,388
119,491 tools
- List creator viewsgetcreatorviews · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List filter presetsgetfilterpresets · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List operationsgetoperations · PURPOSE: 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).
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".ArchiveArchive
PluginrequiredMarketing - List social profile viewsgetsocialprofileviews · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List social profilesgetsocialprofiles · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List view groupsgetviewgroups · PURPOSE: 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, 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;
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".ArchiveArchive
PluginrequiredMarketing - List webhook deliveriesgetwebhookdeliveries · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List webhook eventsgetwebhookevents · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - List webhook subscriptionsgetwebhooksubscriptions · PURPOSE: 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.
- after (optional string): nextCursor from the previous page. All subscriptions fit one page
today (workspace cap), so paging is only needed once caps rise.
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 `after: 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".ArchiveArchive
PluginrequiredMarketing - List workspacesgetworkspaces · PURPOSE: 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".ArchiveArchive
PluginrequiredMarketing - Move collection to groupmovecollectiontogroup · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Move content view to groupmovecontentviewtogroup · PURPOSE: Move a Content View (a saved media-deck view) 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Move creator view to groupmovecreatorviewtogroup · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Move social profile view to groupmovesocialprofileviewtogroup · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Redeliver webhook deliveryredeliverwebhookdelivery · PURPOSE: 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 `failed` DELIVERIES ARE REPLAYABLE: a delivery in any other status (pending / delivering /
succeeded) returns a not_replayable userError (field ["deliveryId"]) and enqueues nothing. An
already-replayed delivery is no longer `failed`, so re-calling is a safe no-op not_replayable
(this is what makes the tool idempotent-safe).
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Refetch engagement (bulk)refetchengagementbulk · PURPOSE: 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 spent are NOT refundable — this spend 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"], ["base"]). Common userError cause: insufficient credits.
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Remove item from collectionsremoveitemfromcollections · PURPOSE: 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 simply ignored (no-op, no error).
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 Collection ids
after the write. `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, workspaces using the OLDER Collection storage only:
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.
SECONDARY, preset-backed workspaces:
searchItems(customAttributeConditions: [
{ field: "collections", operator: "CONTAINS", type: "MULTIPLE_SELECT_V2", value: ["<collection-id>"] }
])
and check the item is absent.
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Reorder views in groupreorderviewsingroup · PURPOSE: Reorder the member views (Content Views + Social Profile Views + Creator Views) 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`, already in position order), then submit
the FULL reordered list. All three view kinds share one position sequence, so a single `viewIds`
list mixes Content View, Social Profile View, and Creator View 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), the entire reorder is REJECTED with a validation_error userError
on ["viewIds"] listing 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 id in `viewIds` returns a validation_error on ["viewIds"] with the
offenders named (and rewrites nothing).
OUTPUT: { viewGroup: { id, name, contentViews, socialProfileViews, creatorViews } | null,
userErrors: [{ field, message }] }.
On success `viewGroup.contentViews` / `.socialProfileViews` / `.creatorViews` 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Resolve item ids by URLgetitemidsbyurl · PURPOSE: Resolve public social-media post URLs to shop-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".ArchiveArchive
PluginrequiredMarketing - Rotate webhook subscription secretrotatewebhooksubscriptionsecret · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Search creatorssearchcreators · PURPOSE: 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 / 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 option `id`s.
* TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values.
* IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null.
Operators by type group (passing an operator outside its group is rejected with a
validation error naming the field; a SHIPPING_ADDRESS-typed condition is always
rejected the same way — that is the SHIPPING_ADDRESS *type*, distinct from the
`shipping_address` *field* whose stored-only matching semantics are noted below):
- 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" } (offset optional); an empty or
unrecognized value makes the condition a no-op.
- 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: two schemas are stored but NOT indexed for filtering — the AI free-text summary
(post_summary) and shipping_address. For post_summary/shipping_address: positive
operators (IS, CONTAINS, STARTS_WITH, etc.) match NOTHING; negated operators
(IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — do not filter on these
fields; read their values from each item's `customAttributes` instead.
NOTE: silently-ignored conditions — on items, conditions on `labels` and `post_date`
are ignored (no error, no filtering effect); on creators, conditions on `full_name`
may be ignored (feature-flag-gated).
Examples:
[{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "value": "<option-uuid>" }]
[{ "field": "links", "operator": "CONTAINS", "type": "TEXT_LIST",
"value": ["https://example.com/promo"] }]
[{ "field": "lead_score", "operator": "BETWEEN", "type": "NUMBER",
"value": { "from": 10, "to": 100 } }]
[{ "field": "notes", "operator": "IS_NOT_EMPTY", "type": "TEXT", "value": null }]
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".ArchiveArchive
PluginrequiredMarketing - Search itemssearchitems · PURPOSE: 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-key explanation for
AI-generated attribute values; pair an entry to its value by key, and call
getCustomAttributeSchemas to see which keys are AI-generated via aiGenerated.
"concise" trims each node to id, originalUrl, provider, type, takenAt, caption,
creator { id }, socialProfile { id accountName },
currentEngagement { likes comments views shares earnedMediaValue } — for listing /
filtering / ranking, and creator.id is the join key for creator-level diffs (see the
CAMPAIGN ROSTER recipe on searchCreators). Use "detailed" when chaining ids (mediaItemId,
externalId), customAttributes, aiFilterReasons, or the full creator / socialProfile
shape are needed.
FILTER EXAMPLE: viral TikTok videos with 10k+ likes published in 2024, most-liked first:
{ "filter": { "provider": "TIKTOK",
"engagement": [{ "field": "LIKE_COUNT", "range": { "from": 10000 }}],
"takenAt": { "from": "2024-01-01T00:00:00Z", "to": "2024-12-31T23:59:59Z" }},
"sorting": [{ "sortKey": "LIKE_COUNT", "sortOrder": "DESC" }], "limit": 20 }
Shape reminder: provider / engagement / takenAt / itemTypes go INSIDE `filter`; sorting,
limit, cursor, presetId are top-level. Every enum value is UPPERCASE.
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 option `id`s.
* TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values.
* IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null.
Operators by type group (passing an operator outside its group is rejected with a
validation error naming the field; a SHIPPING_ADDRESS-typed condition is always
rejected the same way — that is the SHIPPING_ADDRESS *type*, distinct from the
`shipping_address` *field* whose stored-only matching semantics are noted below):
- 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" } (offset optional); an empty or
unrecognized value makes the condition a no-op.
- 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: two schemas are stored but NOT indexed for filtering — the AI free-text summary
(post_summary) and shipping_address. For post_summary/shipping_address: positive
operators (IS, CONTAINS, STARTS_WITH, etc.) match NOTHING; negated operators
(IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — do not filter on these
fields; read their values from each item's `customAttributes` instead.
NOTE: silently-ignored conditions — on items, conditions on `labels` and `post_date`
are ignored (no error, no filtering effect); on creators, conditions on `full_name`
may be ignored (feature-flag-gated).
Examples:
[{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "value": "<option-uuid>" }]
[{ "field": "links", "operator": "CONTAINS", "type": "TEXT_LIST",
"value": ["https://example.com/promo"] }]
[{ "field": "lead_score", "operator": "BETWEEN", "type": "NUMBER",
"value": { "from": 10, "to": 100 } }]
[{ "field": "notes", "operator": "IS_NOT_EMPTY", "type": "TEXT", "value": null }]
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".ArchiveArchive
PluginrequiredMarketing - Send webhook test eventsendwebhooktestevent · PURPOSE: 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).
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update collectionupdatecollection · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update content viewupdatecontentview · PURPOSE: Partial-update a saved content (media deck) 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 (getContentView(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
getContentViews / createContentView). Resolve ids with getContentViews 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: { 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. 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" } }.
For anything not listed above (super_search, location filters, contract-status filters, and
any key you are unsure of), CLONE an existing view's `filters` — do NOT guess key names.
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 option `id`s.
* TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values.
* IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null.
Operators by type group (passing an operator outside its group is rejected with a
validation error naming the field; a SHIPPING_ADDRESS-typed condition is always
rejected the same way — that is the SHIPPING_ADDRESS *type*, distinct from the
`shipping_address` *field* whose stored-only matching semantics are noted below):
- 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" } (offset optional); an empty or
unrecognized value makes the condition a no-op.
- 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: two schemas are stored but NOT indexed for filtering — the AI free-text summary
(post_summary) and shipping_address. For post_summary/shipping_address: positive
operators (IS, CONTAINS, STARTS_WITH, etc.) match NOTHING; negated operators
(IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — do not filter on these
fields; read their values from each item's `customAttributes` instead.
NOTE: silently-ignored conditions — on items, conditions on `labels` and `post_date`
are ignored (no error, no filtering effect); on creators, conditions on `full_name`
may be ignored (feature-flag-gated).
Examples:
[{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "value": "<option-uuid>" }]
[{ "field": "links", "operator": "CONTAINS", "type": "TEXT_LIST",
"value": ["https://example.com/promo"] }]
[{ "field": "lead_score", "operator": "BETWEEN", "type": "NUMBER",
"value": { "from": 10, "to": 100 } }]
[{ "field": "notes", "operator": "IS_NOT_EMPTY", "type": "TEXT", "value": null }]
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update creator viewupdatecreatorview · PURPOSE: 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.
- 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. 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" } }.
For anything not listed above (super_search, location filters, contract-status filters, and
any key you are unsure of), CLONE an existing view's `filters` — do NOT guess key names.
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 option `id`s.
* TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values.
* IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null.
Operators by type group (passing an operator outside its group is rejected with a
validation error naming the field; a SHIPPING_ADDRESS-typed condition is always
rejected the same way — that is the SHIPPING_ADDRESS *type*, distinct from the
`shipping_address` *field* whose stored-only matching semantics are noted below):
- 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" } (offset optional); an empty or
unrecognized value makes the condition a no-op.
- 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: two schemas are stored but NOT indexed for filtering — the AI free-text summary
(post_summary) and shipping_address. For post_summary/shipping_address: positive
operators (IS, CONTAINS, STARTS_WITH, etc.) match NOTHING; negated operators
(IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — do not filter on these
fields; read their values from each item's `customAttributes` instead.
NOTE: silently-ignored conditions — on items, conditions on `labels` and `post_date`
are ignored (no error, no filtering effect); on creators, conditions on `full_name`
may be ignored (feature-flag-gated).
Examples:
[{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "value": "<option-uuid>" }]
[{ "field": "links", "operator": "CONTAINS", "type": "TEXT_LIST",
"value": ["https://example.com/promo"] }]
[{ "field": "lead_score", "operator": "BETWEEN", "type": "NUMBER",
"value": { "from": 10, "to": 100 } }]
[{ "field": "notes", "operator": "IS_NOT_EMPTY", "type": "TEXT", "value": null }]
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update social profile viewupdatesocialprofileview · PURPOSE: 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 cloning an existing view verbatim.
Example content-view filters: { "item_types": ["REEL"], "provider": "INSTAGRAM",
"taken_at": { "from": "2024-01-01", "to": "2024-12-31" } }.
For anything not listed above (super_search, location filters, contract-status filters, and
any key you are unsure of), CLONE an existing view's `filters` — do NOT guess key names.
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 option `id`s.
* TEXT_LIST / NUMBER_LIST / DATE_LIST / ... -> array of values.
* IS_EMPTY / IS_NOT_EMPTY -> value is ignored; pass null.
Operators by type group (passing an operator outside its group is rejected with a
validation error naming the field; a SHIPPING_ADDRESS-typed condition is always
rejected the same way — that is the SHIPPING_ADDRESS *type*, distinct from the
`shipping_address` *field* whose stored-only matching semantics are noted below):
- 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" } (offset optional); an empty or
unrecognized value makes the condition a no-op.
- 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: two schemas are stored but NOT indexed for filtering — the AI free-text summary
(post_summary) and shipping_address. For post_summary/shipping_address: positive
operators (IS, CONTAINS, STARTS_WITH, etc.) match NOTHING; negated operators
(IS_NOT, DOES_NOT_CONTAIN, IS_EMPTY) match EVERY record — do not filter on these
fields; read their values from each item's `customAttributes` instead.
NOTE: silently-ignored conditions — on items, conditions on `labels` and `post_date`
are ignored (no error, no filtering effect); on creators, conditions on `full_name`
may be ignored (feature-flag-gated).
Examples:
[{ "field": "sentiment", "operator": "IS", "type": "SINGLE_SELECT_V2", "value": "<option-uuid>" }]
[{ "field": "links", "operator": "CONTAINS", "type": "TEXT_LIST",
"value": ["https://example.com/promo"] }]
[{ "field": "lead_score", "operator": "BETWEEN", "type": "NUMBER",
"value": { "from": 10, "to": 100 } }]
[{ "field": "notes", "operator": "IS_NOT_EMPTY", "type": "TEXT", "value": null }]
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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update view groupupdateviewgroup · PURPOSE: 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 } | 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Update webhook subscriptionupdatewebhooksubscription · PURPOSE: 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Upload item from URLuploaditemfromurl · PURPOSE: 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 actually resolves to an importable post (public vs private, supported
platform, live vs deleted) is determined asynchronously during ingest — an accepted URL that
never resolves simply 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 ["url"]);
- a non-public host ("Invalid URL — the host is not publicly routable.", field ["url"]);
- the workspace has no user to attribute the import to (field ["shop"]);
- the background enqueue failed (field ["url"], "Failed to enqueue …").
OUTPUT: { success: boolean | null, userErrors: [{ field, message }] }. `userErrors[].field` is
an array of path segments (e.g. ["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 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 spent 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 charges a flat weighted cost (~50+ points) 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".ArchiveArchive
PluginrequiredMarketing - Automate the browserautomate · Start one write-capable browser run and return its run identifier and initial status. A live browser preview is attached to the result.Browser UseBrowser Use
PluginrequiredAI - Continue the browser sessioncontinue · Start one write-capable continuation of an existing browser session and return its run identifier and initial status. A live browser preview is attached to the result.Browser UseBrowser Use
PluginrequiredAI - Poll the browser previewpoll · Poll an active browser preview. Intended only for the live-preview component.Browser UseBrowser Use
PluginrequiredAI - Read the public webread · Start one read-only public-web run and return its run identifier and initial status. A live browser preview is attached to the result.Browser UseBrowser Use
PluginrequiredAI - Wait for the browser resultwait · Wait for and return the result of the existing Browser Use run_id. This never creates a browser run.Browser UseBrowser Use
PluginrequiredAI - Bulk destroy expensesbulk · Delete one or more expenses by their API IDs (e.g. `exp_...`). Best-effort:
critically required expenses linked to external systems (QuickBooks, Sage Intacct, etc.) cannot
be hard-deleted and are returned in the `failed` array with a reason; other
expenses in the same call are still deleted.
The response shape is:
{ deleted_ids: ["exp_...", ...], failed: [{id: "exp_...", reason: "..."}, ...] }
Possible failure reasons:
- "not_found": the id doesn't exist or belongs to a different account
- "not_authorized": the caller lacks permission to delete this expense
- "linked_to_external_system": the expense is mirrored from an external
integration and must be removed there firstInstrumentlInstrumentl
PluginrequiredOperations - Bulk update expensesbulk · Update one or more expenses by their API IDs (e.g. `exp_...`). Best-effort:
expenses that don't exist, belong to a different account, or that the
caller lacks permission to modify are returned in the `failed` array with
a reason; other expenses in the same call are still updated.
Currently the only supported update is toggling whether an expense is
ignored. Ignored expenses are excluded from budget totals but kept on
the account for reference. Pass `ignored: true` to mark expenses as
ignored, or `ignored: false` to restore them.
The response shape is:
{ updated_ids: ["exp_...", ...], failed: [{id: "exp_...", reason: "..."}, ...] }
Possible failure reasons:
- "not_found": the id doesn't exist or belongs to a different account
- "not_authorized": the caller lacks permission to update this expenseInstrumentlInstrumentl
PluginrequiredOperations - Create expensescreate · Add one or more expenses to a grant's budget. Each expense attaches to a
specific expense category (an `ec_...` API ID) within a saved grant (an
`save_...` API ID); call `list_budgets` first to find the right category
and its remaining budget. Amounts are integer cents (e.g. `12500` = $125.00).
Best-effort: each expense is created independently. Items that fail
(unknown or forbidden grant/category, validation errors) are returned in
the `failed` array — each carries its input fields and a reason — while the
rest are still created.
Pass `dry_run: true` to preview the budget impact WITHOUT creating
anything. The preview reports, per category, the projected remaining
budget, whether it would be overspent, and any expenses that already
exist in that category — useful to catch a planned expense the user may
want to update rather than duplicate. Prefer a dry run first whenever an
expense might exceed a budget or already exist.
To update or remove existing expenses, use `bulk_update_expenses` or
`bulk_destroy_expenses` instead of creating new ones.
Create response shape:
{ created_ids: ["exp_...", ...],
failed: [{index, reason, saved_grant_id, expense_category_id, amount_cents, errors?}, ...],
budget_impact: [{expense_category_id, category_name, remaining_cents, overspent}, ...] }
Dry-run response shape:
{ projected: [{saved_grant_id, expense_category_id, category_name,
budgeted_cents, current_remaining_cents, added_cents,
projected_remaining_cents, overspent,
existing_in_category, existing_in_category_has_more}, ...],
failed: [...] }
Possible failure reasons:
- "not_found": the grant or category doesn't exist or belongs to a different account
- "not_authorized": the caller lacks permission to add to this grant
- "invalid": the expense failed validation (details in `errors`)InstrumentlInstrumentl
PluginrequiredOperations - Get current accountget · Retrieve the Instrumentl account the caller is authorized to act in.InstrumentlInstrumentl
PluginrequiredOperations - Hide saved granthide · Hide a previously-saved grant by marking its SavedGrant as no longer
saved (`saved: false, hidden: true`). Pass the SavedGrant's `id` (e.g. `save_...`).
This is reversible and non-destructive: the SavedGrant and any
application data attached to it (submission goals, tasks, expenses,
etc.) are preserved, and the grant can be saved again later.
To find the right `saved_grant_id`, list the project's saved grants
(`list_project_matches` filtered by saved status, or look up via
`list_project_matches`'s `saved` filter).InstrumentlInstrumentl
PluginrequiredOperations - List budgetslist · List the current user's budgets — one per saved grant that has a budget set
up — to decide which budget an expense belongs to. Saved grants with no
expense categories are omitted (unless requested by saved_grant_ids).
Optionally narrow the results (filters compose with AND; omit all to list every
budget). The user usually refers to a budget by name rather than id:
- grant_name: the grant's name (case-insensitive substring) — use when the user names a grant
- project_name: the project's title (case-insensitive substring) — disambiguates when grant names repeat
- saved_grant_ids: budget ids (saved grant ids, prefix "save_") for exact budgets
- transaction_date: an expense date (YYYY-MM-DD) — returns budgets whose award period covers it
Each budget's "id" is its saved grant id (prefix "save_") for use with other
saved-grant tools, plus grant_name and project_title to identify it. A budget
contains budget_phases (each phase is a time window such as a grant year), and
each phase contains expense_categories nested as a tree (child_categories).
All amounts are integer cents (amount_currency holds the currency):
- amount_cents: budgeted amount
- actual_cents: spent so far (excludes planned and ignored expenses)
- planned_cents: planned but not yet actual
- remaining_cents: amount_cents - actual_cents - planned_cents
(negative means over budget)
Category amounts roll up the whole subtree, so a parent category's totals
include its children's. To place an expense, match it to a category by name
and prefer a budget/category whose remaining_cents can cover it.InstrumentlInstrumentl
PluginrequiredOperations - List custom field definitionslist · List the account's custom field definitions — the extra fields this organization has added to its saved grants and funders.
Call this first whenever the user names a field that is not a built-in one ("Priority", "Program Area", "Internal Owner"). It is the only way to resolve such a name to a field and to learn what values that field can hold. Then pass include_custom_fields: true to list_saved_grants or list_funders to read the values themselves.
Each definition returns:
- name: what the user calls the field
- attached_to: which record the field hangs off (SavedGrant, Funder)
- value_type: one of string, text, date, amount, numeric, enum, multiselect, link
- options: for enum and multiselect fields, the allowed choices, each with an option_key (stored), an option_value (shown to people) and a display_order
Values read back from list_saved_grants and list_funders are option_value labels, not option_keys, so compare them against option_value. A multiselect value is those labels joined with ", ".
Filters:
- attached_to: SavedGrant, Funder — narrows to fields on saved grants or on funders
With no filters, returns the account's custom fields a page at a time — page_size defaults to 20. If meta.has_more is true there are more definitions; page with the cursor before concluding a field does not exist.InstrumentlInstrumentl
PluginrequiredOperations - List expenseslist · List the current user's expenses. At least one filter must be provided:
- saved_grant_id: a SavedGrant api_id (prefix "sgrt_")
- expense_ids: an array of Expense api_ids
- project_name: a project title (case-insensitive substring); conditionally joins projects
- grant_name: a grant name (case-insensitive substring); conditionally joins grants
Filters compose with AND. project_name and/or grant_name resolve to the user's matching saved grants and return all expenses across them.InstrumentlInstrumentl
PluginrequiredOperations - List funder contactslist · List all Funder ContactsInstrumentlInstrumentl
PluginrequiredOperations - List funderslist · List all FundersInstrumentlInstrumentl
PluginrequiredOperations - List grantslist · List all grants. This API will only return grants that match one of the following:
1. are custom for your user (created via a spreadsheet upload or custom grant form)
2. saved to a project
3. hidden from a project
4. hidden account-wide.InstrumentlInstrumentl
PluginrequiredOperations - List project matcheslist · List Instrumentl's recommended Grants (Matches) from the public catalog for a Project. A Match is a suggested funder/grant the user has not yet decided on; it is separate from the user's tracker, which is a view of saved grants.
Before calling this tool, disambiguate with the user when the wording could go either way. Users routinely say "my grants/matches/opportunities (for my project X)" to mean entries already in their tracker (SavedGrants → list_saved_grants), and other times to mean fresh recommendations from the catalog (Matches → this tool). Ask a short clarifying question, e.g.: "Do you mean grants/matches already saved in your tracker, or new recommendations Instrumentl has surfaced for this project?" Use this tool only after confirming the user wants recommendations; otherwise call list_saved_grants (it accepts `project_name` or `project_id`).
Filters:
- project_id: a Project api_id (prefix "proj_") to scope recommendations to that project
- match_ids: array of Match api_idsInstrumentlInstrumentl
PluginrequiredOperations - List projectslist · List all projects associated with the current accountInstrumentlInstrumentl
PluginrequiredOperations - List saved grantslist · List the user's SavedGrants — entries already in their tracker. A SavedGrant is the user's record of pursuing a Grant for a specific Project; an "awarded" SavedGrant is one with status "funding_awarded" or "closed".
Before calling, disambiguate when the wording is ambiguous; ask a single clarifying question rather than guessing. The most common confusion is between SavedGrants (already in the tracker) and Matches (new recommendations from the public catalog). A good clarifier: "Do you mean grants/matches already in your tracker, or new recommendations Instrumentl has surfaced for this project?"
- "my grant(s)", "my match(es)", "my opportunity/opportunities", "grants for my project X", "what have I saved/tracked": almost always SavedGrants already in the tracker → THIS tool. If the phrasing could plausibly mean "new recommendations" instead, confirm with the user first; use list_project_matches only after they confirm they want recommendations.
- "awards", "current grants", or bare "grants" in an award/funding context: awarded SavedGrants → THIS tool with `awarded: true` (matches both "Awarded - Active" and "Awarded - Closed"). For only currently-active awards, pass `statuses: ["funding_awarded"]` instead.
- "find grants", "search grants", "what grants are available": the public Grant catalog → use list_grants.
- Bare "grants" with no "my" and no award context: ambiguous — ask first.
Filters (compose with AND):
- saved_grant_id: a SavedGrant api_id (prefix "save_") — returns just that one
- project_id / grant_id / funder_id: api_ids to scope by the related record
- statuses: array of SavedGrant statuses (researching, planned, started, loi_in_progress, submitted, loi_submitted, funding_awarded, closed, declined, abandoned)
- awarded: true restricts to awarded SavedGrants; false excludes them
- award_year: integer fiscal year for the award period
- project_name: case-insensitive substring on the project title (use when the user names a project)
- grant_name: case-insensitive substring on the underlying grant's name (use when the user names a grant)
Custom fields: set include_custom_fields to true to get each saved grant's custom field values. Call list_custom_field_definitions first to learn which fields the account has and what their options are. Values come back as labels, not option keys, so compare them against a definition's option_value.
To list everything in the user's tracker, call with no filters.InstrumentlInstrumentl
PluginrequiredOperations - List taskslist · List the user's Tasks (also called custom grant deadlines). A Task belongs to a SavedGrant in the user's tracker and can have an assignee, a nominator, a creator, and (when done) a completer.
DISAMBIGUATE before filtering by a person. The tool exposes four separate people filters — `assignee`, `nominator`, `created_by`, `completed_by` — because each captures a different relationship and they AND-compose, so picking the wrong one returns wrong results. When the user names a person without a clear role ("Sarah's tasks", "tasks for Sarah", "what is Sarah working on"), ask which they mean before calling. A good single clarifier:
"Do you mean tasks assigned to Sarah, tasks she created, tasks she completed, or tasks she nominated (handed off)?"
Common phrasings → likely role:
- "my tasks", "Sarah's tasks", "tasks for Sarah", "what is Sarah working on": almost always `assignee` — confirm and proceed.
- "tasks Sarah added/made/wrote": `created_by`.
- "tasks Sarah finished/completed/closed out": `completed_by` (also implies done).
- "tasks Sarah handed off / reassigned / passed to someone else": `nominator`.
If unclear, ask before guessing. Each people filter matches first name, last name, OR email (case-insensitive substring), so the user need not say which form they remember.
Other filters (compose with AND):
- saved_grant_id / project_id: api_ids — use when you already have the id (e.g. from another tool call)
- project_name / grant_name / funder_name: case-insensitive substrings — use when the user names the entity
- assignee_id / nominator_id: user api_ids — for an exact person rather than a name search
- is_done: true returns completed tasks; false returns open tasks ("show open tasks" is the most common ask)
- past_due: true returns overdue, not-done tasks; false excludes them
- unassigned: true returns tasks with no assignee (use for "tasks nobody owns")
- kinds: array of task kinds (full_proposal, report, general, letter_of_inquiry, cultivation)
- deadline_gte / deadline_lte: ISO dates; deadline_gte INCLUDES tasks with no deadline, deadline_lte does not
Call with no filters to list every task in the user's tracker.InstrumentlInstrumentl
PluginrequiredOperations
What is Tool Explorer?
Tool Explorer indexes the callable tool names and descriptions attached to public registry profiles. It is useful for seeing what agents can actually invoke, not just which profile exists.
How do category and verb filters work?
Category filters use the live registry category rollup. Verb filters use the public tool insights rollup, so the page stays backed by the same read models as the tracker charts.
Why do auth requirements matter?
Auth requirements show whether a tool is likely usable without account connection, requires authentication, is private, or is unknown in the current snapshot.