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_transactionslist · List a group's transactions, newest first. Each transaction carries its from/to legs already resolved to display names, so one call is enough to tell the user '1200 for dinner, paid by Ming'.
This is where transaction ids come from: call it before update_transaction or delete_transaction.
Paging: pass the previous page's `next_cursor` back verbatim as `cursor`, and stop when next_cursor comes back null. The cursor is opaque — never construct or edit one. Narrow with `from`/`to` when the user names a day or a period. The group to act on. Accepts either a Lightsplit group id or any Lightsplit share link (web, LINE or Telegram) — pass through whatever the user gave you, unchanged.LightsplitLightsplit
PluginrequiredFinance - remove_memberremove · Remove an unseated placeholder member (is_seated=false) from a group.
Three refusals worth knowing before you call:
- Only the group's OWNER may remove anyone. Anybody else is refused before either of the checks below is reached, so a non-owner gets this answer whatever the member looks like.
- A SEATED member — a real Lightsplit account holder, including the caller themselves — cannot be removed here. They have to leave the group themselves in the Lightsplit app; say so rather than trying again.
- A member with a non-zero balance cannot be removed. Settle them up first (see get_settle_transfers and create_transfer), then remove them.
Member ids come from get_group; call it first if you do not already have this group's roster. In that roster is_caller marks the member row belonging to the person whose token this is, which is who 'I', 'me' and 'my share' refer to.LightsplitLightsplit
PluginrequiredFinance - resolve_group_linkresolve · Turn a Lightsplit share link into a group id, and return the same group's links for the other channels.
You rarely need this: every other tool's `group` argument already accepts a share link. Reach for it when the user has a link and wants the same group on other channels ('send me the LINE version') — and hand back every link that comes with it, not just the one they named, so they can still choose. It only succeeds for a group the caller may already see, so it cannot be used to probe whether a link is real.LightsplitLightsplit
PluginrequiredFinance - set_group_budgetset · Set (or clear) the group's shared spending budget — the cap everyone in the group sees.
Only the group's OWNER may set it; anyone else is refused. To set one, pass `amount` (>= 0, in the group's primary currency) and `period_type` ('monthly' or 'all_time'). To CLEAR it, omit both `amount` and `period_type`. The group to act on. Accepts either a Lightsplit group id or any Lightsplit share link (web, LINE or Telegram) — pass through whatever the user gave you, unchanged.LightsplitLightsplit
PluginrequiredFinance - set_personal_budgetset · Set (or clear) the caller's OWN personal spending budget in a group — private to them, not the shared group budget.
The caller must be a member of the group. To set one, pass `amount` (>= 0, in the group's primary currency) and `period_type` ('monthly' or 'all_time'). To CLEAR it, omit both. The group to act on. Accepts either a Lightsplit group id or any Lightsplit share link (web, LINE or Telegram) — pass through whatever the user gave you, unchanged.LightsplitLightsplit
PluginrequiredFinance - uncomplete_reminderuncomplete · Reopen a reminder that was ticked off, putting it back on the group's active list. Get the reminder_id from list_reminders.LightsplitLightsplit
PluginrequiredFinance - update_groupupdate · Rename a group, change its note, or change who may open its share links. Only the fields you pass are changed; everything else is left alone.
This is where you open a group up for sharing: other people can only get in through a share link, and only `public_permission` 'login' lets them claim a seat.
Only the group's owner may do this. The group to act on. Accepts either a Lightsplit group id or any Lightsplit share link (web, LINE or Telegram) — pass through whatever the user gave you, unchanged.LightsplitLightsplit
PluginrequiredFinance - update_memberupdate · Rename a member of a group, or change their permission between 'edit' and 'view'.
Renaming only has a visible effect on an UNSEATED placeholder member (is_seated=false). Once a real Lightsplit account claims the seat, that account's own display name is shown everywhere and `name` is ignored.
Only the group's owner may do this. Member ids come from get_group; call it first if you do not already have this group's roster. In that roster is_caller marks the member row belonging to the person whose token this is, which is who 'I', 'me' and 'my share' refer to.LightsplitLightsplit
PluginrequiredFinance - update_reminderupdate · Edit a reminder. Editing REPLACES the reminder wholesale, so pass `what` as the full text you want it to read, not just the change. Get the reminder_id from list_reminders.
`currency` and `amount` go together, exactly as in create_reminder: pass both to set (or change) the reminder's price, omit BOTH to leave it a plain reminder with no price.LightsplitLightsplit
PluginrequiredFinance - update_transactionupdate · Edit an existing transaction. Only the fields you pass change; everything else is left alone. Get the transaction_id from list_transactions.
ONE RULE: if you change `amount` or `currency`, you must ALSO say how the transaction now divides — pass `splits`, or `equal_split: true`. `payers` on its own does not satisfy it. And if the transaction has MORE THAN ONE payer and the amount changes, restate `payers` as well: one payer scales to the new total unambiguously, several do not. Rescaling the old division to a new total would have to guess, and the guess is somebody's money.
On a TRANSFER, `receiver_member_id` retargets who received the money, and `splits` and `equal_split` are rejected, because a transfer is not divided at all; on an expense or an income it is `receiver_member_id` that is rejected. Passing an argument that means nothing for the transaction's type is rejected rather than ignored, so a refusal here means the edit did not half-happen.
A member whose seat has since been deactivated can still be named on a transaction they were already part of — correcting last year's dinner does not require re-activating everybody who was at it. Adding a deactivated member who was NOT already on the transaction is refused.
A transaction's KIND cannot be changed — an expense cannot become an income or a transfer — and a transaction cannot be moved to another group. For either, delete it and create it again with the right tool instead.
HOW THE MONEY IS DIVIDED
`splits` says who the money was FOR:
- omit `splits` entirely -> split equally among ALL active members of the group
- [{member_id}, {member_id}] -> split equally among just those people
- [{member_id, share: 2}, {member_id, share: 1}] -> weighted, so the first person carries a double portion
- [{member_id, amount: 300}, {member_id, amount: 900}] -> exact figures; then EVERY entry needs an amount and they must add up to `amount`
Mixing `share` and `amount` in one call is rejected rather than guessed at.
`payers` says who PUT THE MONEY IN:
- omit `payers` -> the caller (the member with is_caller=true) paid all of it
- [{member_id}] -> that one member paid all of it
- [{member_id, amount}, ...] -> several payers; every one needs an amount and they must add up to `amount`
Every figure is POSITIVE. The direction of the money comes from which tool you call, never from a minus sign.
WHEN YOU REPORT THE RESULT, give the user the reply's `links` — every non-null one, labelled by channel — so they can open the transaction and check it.LightsplitLightsplit
PluginrequiredFinance - Get tour detailsget · Open the tour-detail widget for one tour by id. Use only with tour_id from search_tours.FREETOUR.comFREETOUR.com
PluginnoneTravel & Hospitality - Search tourssearch · Search bookable tours and activities on freetour.com (all types — walking, bike, food, bus, day trips, and more). Filter by `categories` only when the user asks for a specific tour type. Search by city, country, date range, start time, duration, guide language, price range, rating and keywords. Always pass `locale`: en, es, pt, de, fr, or it — the language the user wrote in. Never infer locale from the city. When locale is en (including unsupported user languages), reply in English. UNSUPPORTED USER LANGUAGE (not en/es/pt/de/fr/it): always pass locale: "en". Put only English theme keywords in query (gothic quarter, tapas) — never the user's full sentence or non-English words. Map price, time, duration, category, destination (`city`/`cities`/`country`), and guide-language intent to the matching tool fields — not query. Reply to the user in English; tour cards and widget labels will be in English. START TIME FILTERING (when the user mentions a time or part of day): Use time_from and time_to as integers 0–23 (whole hours only, inclusive). Never put time words in `query` — use time_from/time_to. Exact hour H ('at 10', 'um 10 Uhr', 'a las 10'): time_from: H, time_to: H. Range A–B ('from 10 to 12', 'between 10am and 12pm'): time_from: A, time_to: B. After H ('after 10', 'from 10am'): time_from: H (omit time_to). Until / before H inclusive ('before 10', 'until 10am'): time_to: H (omit time_from). Morning ('morning', 'morgens', 'matin'): time_from: 0, time_to: 11. Afternoon ('afternoon', 'nachmittags', 'tarde'): time_from: 12, time_to: 16. Evening ('evening', 'abends', 'soir'): time_from: 17, time_to: 23. 12 PM → 12, 12 AM → 0, 3 PM → 15. When time_from or time_to is set and no date is given, also pass date_from (today) so the API can match scheduled events. DATE FILTERING: use `date_from` and `date_to` as YYYY-MM-DD. Never put dates in `query`. Single calendar day ('tomorrow', 'завтра', 'mañana', 'demain', 'on July 3'): set BOTH date_from and date_to to that same day. Day after tomorrow ('післязавтра', 'pasado mañana', 'übermorgen'): same day in both fields. Today ('today', 'сьогодні', 'hoy', 'aujourd'hui'): both fields = today. Explicit range ('from July 1 to July 5', '2026-07-01 to 2026-07-05'): date_from = earlier day, date_to = later day. Next week ('next week', 'наступного тижня'): date_from = Monday, date_to = Sunday of next week. This weekend ('this weekend', 'ці вихідні'): date_from = Saturday, date_to = Sunday. Open-ended start ('tours from July 1' with no end): date_from only — server extends date_to by 7 days. Compute relative dates from the user's local today when calling the tool. DURATION FILTERING (when the user mentions tour length): Use duration_from and duration_to as integers in MINUTES (inclusive). Never put duration words in `query`. Exact length L ('1 hour', '90 minutes'): duration_from: L, duration_to: L (1h=60, 1.5h=90, 2h30=150). Range A–B ('2-3 hours', 'von 2 bis 3 Stunden'): duration_from: A*60, duration_to: B*60. At least H ('at least 2h', '2+ hours'): duration_from: H*60. Up to H inclusive ('up to 2 hours', 'max 2h'): duration_to: H*60. Shorter than H ('under 2h'): duration_to: H*60-1. Longer than H ('over 3 hours'): duration_from: H*60+1. Short tour: duration_to: 90. Long tour: duration_from: 180. 1–2 hours: duration_from: 60, duration_to: 120. 2–3 hours: duration_from: 120, duration_to: 180. GUIDE LANGUAGE FILTERING (when the user wants tours conducted in a specific language): Use language_codes as an array of ISO 639-1 codes (e.g. ["ja"] for Japanese, ["es"] for Spanish). Never put language names in `query` — use language_codes. This filters which language the guide speaks; it is NOT the same as `locale` (widget/response language). NEVER set locale to the guide language — locale is only how the USER wrote their message. Example: user writes in English 'find tours in London in Spanish' → locale: "en", city: "London", language_codes: ["es"] (cards in English, Spanish flag on tour). Wrong: locale: "es" for that query — that translates the card UI to Spanish. Example: 'tours in Japanese' → language_codes: ["ja"], locale from prompt language (en if English). Multiple options ('English or Spanish'): language_codes: ["en", "es"]. Supported guide-language codes: en, es, pt, de, fr, it, nl, pl, ru, uk, cs, da, sv, fi, el, hu, ro, hr, tr, ar, he, hi, ja, ko, zh, th, id, fa. Do not confuse with destination country ('tours in Spain' → country: Spain, not language_codes). PRICE FILTERING (mandatory when the user mentions price or budget): Prices are integers in EUR. Never put price words in `query` — use price_from, price_to, free_only, or paid_only. Exact price X ('for X euros', 'por X€'): price_from: X, price_to: X. Free / tip-based ('free', 'gratis', 'kostenlos'): free_only: true (same as price_from: 0, price_to: 0). Range X–Y ('between X and Y', 'von X bis Y'): price_from: X, price_to: Y. From X upward ('from €X', 'ab X€', 'X+'): price_from: X (omit price_to). Up to X inclusive ('up to €X', 'X or less'): price_to: X and price_from: 0. Cheaper than X / less than X ('under X', 'moins de X'): price_to: X-1 and price_from: 0. More expensive than X ('over €X', 'más de X'): price_from: X+1. Paid only ('paid', 'not free'): paid_only: true (same as price_from: 1). If the user mentions ANY price constraint, set the appropriate fields before calling search_tours — never search by city alone. RATING FILTERING (mandatory when the user mentions rating, score, reviews or stars): Ratings are floats 0–10 on the freetour scale, never a 5-star scale. Never put rating words in `query` — use rating_from and rating_to. Named score X ('rating 6', 'rated 8', '8/10'): rating_from: X, rating_to: X.9 — the X.0–X.9 band, so 'rating 6' must not return a 9.5 tour. Decimal score X ('9.8', 'exactly 9.8'): rating_from: X, rating_to: X. At least X ('8+', 'X or more', 'from X'): rating_from: X, rating_to: 10. Range A–B ('between 8 and 10'): rating_from: A, rating_to: B. Up to X inclusive: rating_to: X, rating_from: 0. Below X ('under 7'): rating_to: X-1 (X-0.1 for a decimal), rating_from: 0. Highly rated / top rated / best rated: rating_from: 8, rating_to: 10. Stars are a minimum on the 0–10 scale: 5 stars → rating_from: 9, 4 → 8, 3 → 6, 2 → 4, 1 → 2, each with rating_to: 10. Never map 5 stars to 10. Do not confuse rating with price (needs €/euro), start time (needs am/pm/morning/at H) or duration (needs hours/minutes). If the user mentions ANY rating constraint, set these fields before calling search_tours, and keep every other filter they mentioned. CATEGORY FILTERING: pass `categories` as English API names (never translated). Allowed: Walking Tour, Online Tour, City Ride Tours, Day Trips, Night Life, Food & Drink, Running Tour, Nature & Wildlife, Cruises & Boat, Other, Top Picks. Map user intent even if they wrote another language (e.g. 'tours a pie' → Walking Tour, 'gastronómicos' → Food & Drink, bike → City Ride Tours, boat/cruise → Cruises & Boat). Legacy labels like Bike Tour / Cruises / Nature Tours are normalized server-side to the current API names. Do not default to Walking Tour — omit `categories` to search all types unless the user asks for one. Do not put category type words in `query` when `categories` is set — keep `query` for themes/neighbourhoods only. CITY FILTERING: pass destination cities in English (Barcelona, Berlin, Rome). Put destinations in `city` (one city) or `cities` (array) — not in `query`. For one city use `city` or `cities` with a single element. MULTI-CITY: when the user names several cities (e.g. 'Barcelona and Madrid', 'Paris ou Lyon', 'Berlin und München'), pass cities: ["Barcelona", "Madrid"] in one search_tours call — do not join them into one string. Foreign or local spellings are normalized server-side when possible (Barcelone → Barcelona, Roma → Rome, Praha → Prague, München → Munich). Omit `country` when `city` or `cities` is set. If the city is ambiguous or missing, ask the user to clarify before searching. COUNTRY FILTERING: use `country` when the user wants tours across a whole country without naming a city (e.g. 'tours in Spain', 'find tours in France', 'знайди тури в Іспанії'). Pass the English country name (Spain, France, Germany) — foreign spellings are normalized server-side (España, Spanien, Іспанія → Spain). Put the country in `country`, not in `query` or `language_codes`. 'Tours in Spanish' means guide language → language_codes: ["es"]; 'tours in Spain' means destination → country: "Spain". Omit `country` when `city` or `cities` is set. One country per search — for multiple countries, run separate searches or ask which country. DESCRIPTION FEATURES: use `must_have` / `must_not_have` for hard requirements or exclusions that must appear (or not appear) in the tour description text. Pass short English keywords only (e.g. guide, tapas, wheelchair, bus, crowded). Translate from the user's language to English. Do not use for tour type (use `categories`), price (use price_* / free_only), guide language (use language_codes), or neighbourhood themes (use `query`). Examples: 'with a guide' → must_have: ["guide"]; 'not crowded' → must_not_have: ["crowded"]; 'no bus' → must_not_have: ["bus", "coach"]. Returns tour cards with ratings, prices and direct booking links.FREETOUR.comFREETOUR.com
PluginnoneTravel & Hospitality - Get recent marine newsget · Retrieve the most recent public marine news published by VitaMarinaWeb. Use this for recent updates, not for a keyword search.VitamarinawebVitamarinaweb
PluginnoneConsumer & Lifestyle - Read a marine articleget · Retrieve one public VitaMarinaWeb marine article using the numeric article ID returned by search_articles or get_news.VitamarinawebVitamarinaweb
PluginnoneConsumer & Lifestyle - Search marine articlessearch · Search public VitaMarinaWeb articles about marine species, marine biology, fishing and ocean ecosystems. Use this when the user provides a topic or keyword and wants matching articles.VitamarinawebVitamarinaweb
PluginnoneConsumer & Lifestyle - Search marine speciesfind · Search public marine species entries in the VitaMarinaWeb atlas by scientific or common name. Use this only when the user provides a species name or part of a name.VitamarinawebVitamarinaweb
PluginnoneConsumer & Lifestyle - Add item to collectionsadditemtocollections · PURPOSE: Add an item to one or more Collections in the current workspace. A Collection is
a workspace-scoped tag set (a saved multi-select tag) applied to items; adding an item to
a Collection tags it, so the item then appears when that Collection is used as a filter.
INPUT:
- itemId (required string): the Archive item id to tag.
- collectionIds (array of strings, default []): Collection ids to add the item to.
- collectionNames (array of strings, default []): Collection names to add the item to,
matched case-sensitively within the workspace.
- autoCreate (boolean, default false): when true, a name in `collectionNames` that does
not match an existing Collection creates a new Collection; when false, unmatched names
are dropped and, if NO collection resolves at all, the call returns a
"No valid collections found" userError.
You MUST provide at least one of `collectionIds` or `collectionNames`. Providing neither
returns a userErrors entry ("Provide 'collectionIds' or 'collectionNames'") and makes no change.
RESOLVING IDS vs NAMES: prefer ids. Resolve Collection ids with getCollections first, then
pass them as `collectionIds`. Use the ids returned by getCollections for THIS workspace:
several id forms are accepted depending on how the workspace stores Collections. An id
from another workspace, or one this workspace cannot write through this API, rejects the
WHOLE call (all-or-nothing) with a userErrors entry naming the offending id under
`collectionIds`. An id that exists in the workspace but is not writable here reports
"exists in this workspace but is not writable through this API" — re-resolve via
getCollections and retry with the id it returns.
AUTO_CREATE: use `autoCreate: true` only when creating a Collection is intended. It creates
the new Collection in this workspace's own Collection storage. A blank name is rejected.
Resolve names via a read tool first when you mean to reference an existing Collection — a
typo with `autoCreate: true` silently creates an unwanted Collection.
OUTPUT: { item: { id, customAttributes } | null, userErrors: [{ field, message }] }.
`item` is the item as stored after the write. In workspaces whose Collections use the
older storage, `item.customAttributes.collections` does NOT reflect Collection
membership and is returned unchanged — use getCollection(id:).itemCount to confirm the
write. In preset-backed workspaces that array holds the item's Collection ids after the
write. `userErrors[].field` is an array of path segments (e.g. ["collectionIds"]).
VERIFY AFTER WRITE:
PRIMARY (works in every workspace): getCollection(id: "<collection-id>") — its
`itemCount` reflects the change immediately. Default to this check.
SECONDARY, workspaces using the OLDER Collection storage only:
searchItems(filter: { collectionsIds: ["<collection-id>"] })
with a Collection id from getCollections, and check the item is present. Search results
lag a write by up to about a minute, so this is confirmation, not the immediate check.
SECONDARY, preset-backed workspaces:
searchItems(customAttributeConditions: [
{ field: "collections", operator: "CONTAINS", type: "MULTIPLE_SELECT_V2", value: ["<collection-id>"] }
])
and check the item is present.
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 - Create collectioncreatecollection · PURPOSE: Create a Collection (a saved tag set) in the current workspace. A Collection is a
workspace-scoped multi-select tag applied to items; once created you tag items into it with
addItemToCollections and filter items by it.
INPUT:
- name (required string): the display name for the new Collection. Names are UNIQUE within
the workspace.
NAME UNIQUENESS: if a Collection with the given name already exists, the call returns a
duplicate-name userError (field ["input","name"]) and creates NOTHING. Do NOT invent a name
variant ("Summer 2", "Summer_new") to work around it — REUSE the existing Collection instead:
look it up with getCollections, take its id, and proceed with that id.
AVAILABILITY: creating a Collection is only possible in workspaces whose Collections use the
standard storage. In a workspace that stores Collections in an older format the call returns a
schema-missing userError and creates nothing; there, create the Collection by tagging an item
into it directly — addItemToCollections with collectionNames plus autoCreate: true creates the
named Collection as part of the tagging call.
OUTPUT: { collection: { id, name, itemCount } | null, userErrors: [{ field, message }] }.
`collection.id` is the Collection's id in this workspace — pass it to
addItemToCollections / removeItemFromCollections to tag items, and to searchItems(presetId:)
or getCollection(id:) to read it. `collection` is null on a userErrors failure.
`userErrors[].field` is an array of path segments (e.g. ["input","name"]).
VERIFY AFTER WRITE: confirm the Collection landed with getCollection(id:) using the returned
`collection.id`.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Create content viewcreatecontentview · PURPOSE: Create a saved content (media deck) view in the current workspace. A content view
stores a filter set once; read its items later with searchItems(presetId:) instead of
re-sending filters each call.
CLONE FIRST (strongly preferred): the `filters` blob is opaque and easy to get subtly wrong.
Before composing filters from scratch, fetch an existing content view with getContentViews (or
getContentView(id:)), copy its `filters` value, and adapt it. A cloned-and-tweaked blob renders
correctly in the app; a hand-built one may look successful here yet render broken. Only build
filters from scratch for simple views, using the VIEW FILTERS contract below.
INPUT:
- name (required string): the display name. Names are NOT required to be unique — two content
views may share a name.
- filters (required object): the opaque filter blob (see VIEW FILTERS below). Pass {} for a
view with no filters (all items).
- customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE
FILTERS below). Defaults to [].
- sort (optional array): sort directives, e.g. [{ "field": "taken_at", "direction": "desc" }].
Defaults to [].
- showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults
to true.
OUTPUT: { contentView: { id, name, filters, customAttributeConditions, sort, showReportingStats,
group } | null, userErrors: [{ field, message }] }.
`contentView.id` is the view's canonical FilterPreset UUID — pass it to searchItems(presetId:)
to read its items, or to getContentView(id:) to read it back. `contentView` is null on a
userErrors failure. `userErrors[].field` is an array of path segments (e.g. ["input","name"]).
VERIFY AFTER WRITE: confirm the view landed with getContentView(id:) using the returned
`contentView.id`.
VIEW FILTERS (the `filters` blob):
`filters` is a JSON object mapping filter keys to values, stored verbatim. 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 - Create creator viewcreatecreatorview · PURPOSE: Create a saved Creator View in the current workspace. A Creator View saves a
reusable narrowing (customAttributeConditions + sort) once; read its creators later with
creators(presetId:). NOTE: the `filters` blob is stored and echoed back but NOT applied on
read — creators(presetId:) narrows ONLY by customAttributeConditions + sort (see VIEW FILTERS
below). Use customAttributeConditions to actually narrow results.
CLONE FIRST (strongly preferred): the `filters` blob is opaque and easy to get subtly wrong.
Before composing filters from scratch, fetch an existing Creator View with getCreatorViews (or
getCreatorView(id:)), copy its `filters` value, and adapt it. A cloned-and-tweaked blob renders
correctly in the app; a hand-built one may look successful here yet render broken. Only build
filters from scratch for simple views, using the VIEW FILTERS contract below.
INPUT:
- name (required string): the display name. Names are NOT required to be unique — two Creator
Views may share a name.
- filters (required object): the opaque filter blob (see VIEW FILTERS below) — stored and
echoed back but NOT applied on read. Pass {} unless cloning an existing view verbatim.
- customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE
FILTERS below). Defaults to [].
- sort (optional array): sort directives. Defaults to [].
- showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults
to true.
OUTPUT: { creatorView: { id, name, filters, customAttributeConditions, sort,
showReportingStats, group } | null, userErrors: [{ field, message }] }.
`creatorView.id` is the view's canonical FilterPreset UUID — pass it to creators(presetId:) to
read its creators, or to getCreatorView(id:) to read it back. `creatorView` is null on a
userErrors failure. `userErrors[].field` is an array of path segments (e.g. ["input","name"]).
VERIFY AFTER WRITE: confirm the view landed with getCreatorView(id:) using the returned
`creatorView.id`.
VIEW FILTERS (the `filters` blob):
`filters` is a JSON object mapping filter keys to values, stored verbatim. 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 - Create social profile viewcreatesocialprofileview · PURPOSE: Create a saved Social Profile view in the current workspace. A Social Profile view
stores a filter set once; read its profiles later with getSocialProfiles(presetId:) instead of
re-sending filters each call.
CLONE FIRST (strongly preferred): the `filters` blob is opaque and easy to get subtly wrong.
Before composing filters from scratch, fetch an existing Social Profile view with
getSocialProfileViews (or getSocialProfileView(id:)), copy its `filters` value, and adapt it. A
cloned-and-tweaked blob renders correctly in the app; a hand-built one may look successful here
yet render broken. Only build filters from scratch for simple views, using the VIEW FILTERS
contract below.
INPUT:
- name (required string): the display name. Names are NOT required to be unique — two Social
Profile views may share a name.
- filters (required object): the opaque filter blob (see VIEW FILTERS below). Pass {} for a
view with no filters (all profiles).
- customAttributeConditions (optional array): narrows the filter set (see CUSTOM ATTRIBUTE
FILTERS below). Defaults to [].
- sort (optional array): sort directives. Defaults to [].
- showReportingStats (optional boolean): whether reporting stats surface in the UI. Defaults
to true.
OUTPUT: { socialProfileView: { id, name, filters, customAttributeConditions, sort,
showReportingStats, group } | null, userErrors: [{ field, message }] }.
`socialProfileView.id` is the view's canonical FilterPreset UUID — pass it to
getSocialProfiles(presetId:) to read its profiles, or to getSocialProfileView(id:) to read it
back. `socialProfileView` is null on a userErrors failure. `userErrors[].field` is an array of
path segments (e.g. ["input","name"]).
VERIFY AFTER WRITE: confirm the view landed with getSocialProfileView(id:) using the returned
`socialProfileView.id`.
VIEW FILTERS (the `filters` blob):
`filters` is a JSON object mapping filter keys to values, stored verbatim. It is OPAQUE and
unvalidated on write — an invalid blob is accepted but may render the view broken in the
app. STRONGLY PREFER cloning an existing view's `filters` (fetch it with the matching get*
tool) over composing from scratch. Pass {} for "no filters".
CONTENT-VIEW keys (createContentView / updateContentView) — applied when the view is read
via items(presetId:) / searchItems(presetId:):
- item_types : array of item types, e.g. ["POST","REEL","STORY","SHORT"].
- provider : one of "INSTAGRAM","TIKTOK","YOUTUBE".
- content_types : array of media types, e.g. ["IMAGE","VIDEO"].
- taken_at : publication-date range { "from": ISO8601, "to": ISO8601 }.
- virality_score : array of "VIRAL","HIGH","MEDIUM","LOW".
- tags_names : array of hashtag / mention tag names.
- campaigns_ids : array of campaign ids.
- collections_ids : array of Collection ids (as returned by getCollections).
- social_profile_ids : array of social profile ids.
- account_names : array of social profile handles.
- followers_count : follower-count range { "from": int, "to": int }.
SOCIAL-PROFILE-VIEW `filters` (createSocialProfileView / updateSocialProfileView): the blob
is stored and echoed back by the get* tools, but it is NOT applied when reading profiles —
socialProfiles(presetId:) / getSocialProfiles(presetId:) filter ONLY by the view's
customAttributeConditions and sort.
To make a Social Profile View that actually narrows results, use customAttributeConditions
(see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters unless cloning an existing
view verbatim.
CREATOR-VIEW `filters` (createCreatorView / updateCreatorView): same caveat — the blob is
stored and echoed back by the get* tools, but it is NOT applied when reading creators.
creators(presetId:) / searchCreators(presetId:) narrow ONLY by the view's
customAttributeConditions and sort. To make a Creator View that actually narrows results,
use customAttributeConditions (see CUSTOM ATTRIBUTE CONDITIONS above); pass {} for filters
unless cloning an existing view verbatim.
Example content-view filters: { "item_types": ["REEL"], "provider": "INSTAGRAM",
"taken_at": { "from": "2024-01-01", "to": "2024-12-31" } }.
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 - Create view groupcreateviewgroup · PURPOSE: Create a user-created view group in the current workspace. A view group is a
workspace-scoped sidebar bucket that Content Views, Social Profile Views, and workspace
Creator Views can be moved into (with moveContentViewToGroup / moveSocialProfileViewToGroup /
moveCreatorViewToGroup). CAMPAIGN Creator Views are NEVER group members — they are
campaign-scoped, not workspace-scoped.
INPUT:
- name (required string): the display name for the new group. Names are UNIQUE within the
workspace.
NAME UNIQUENESS: if a group with the given name already exists, the call returns a
validation_error userError (field ["input","name"]) and creates NOTHING. Do NOT invent a name
variant ("Team 2", "Team_new") to work around it — REUSE the existing group instead: look it up
with getViewGroups, take its id, and move views into it with the move tools.
OUTPUT: { viewGroup: { id, name, contentViews, socialProfileViews, creatorViews } | null,
userErrors: [{ field, message }] }.
`viewGroup.id` is the group's canonical UUID — pass it as `groupId` to
moveContentViewToGroup / moveSocialProfileViewToGroup / moveCreatorViewToGroup to fill it, to
reorderViewsInGroup to order its members, or to getViewGroup(id:) to read it back. A freshly
created group has empty `contentViews` / `socialProfileViews` / `creatorViews`. `viewGroup` is
null on a userErrors failure.
`userErrors[].field` is an array of path segments (e.g. ["input","name"]).
VERIFY AFTER WRITE: confirm the group landed with getViewGroup(id:) using the returned
`viewGroup.id`.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Create webhook subscriptioncreatewebhooksubscription · PURPOSE: Create an outbound-webhook subscription in the current workspace. New items landing in
the subscribed content view(s) trigger a signed HTTPS POST to your endpoint.
INPUT:
- name (required string): display name for the subscription.
- url (required string): the HTTPS endpoint deliveries are POSTed to. Must be HTTPS and pass
SSRF validation (no private / link-local hosts) — a bad url returns a userError on ["input","url"].
- viewIds (array of strings): content-view (FilterPreset) UUIDs whose new items trigger
deliveries. REQUIRED (at least one, each owned by this workspace — resolve ids with
getContentViews first) whenever any eventType is view-scoped, which is every subscribable
type in v1; omit it only for a non-view event type (none exist yet). Omitting it with a
view-scoped type returns a viewIds userError on ["input","viewIds"].
- eventTypes (optional array of strings): event types to subscribe to. Defaults to
["content_view.item_added"] (the only supported type in v1).
- metadata (optional object): opaque metadata echoed back in each delivery payload. Max 4KB
serialized. Defaults to {}.
DUPLICATES: creating an exact duplicate (same url AND same event types AND same viewIds
as an existing subscription, in any status) returns a userError on ["input","url"] naming
the existing subscription id instead of creating a second subscription. Reusing a url and
event types for a DIFFERENT set of viewIds is allowed — those deliver different views.
PARTIAL OVERLAP IS NOT BLOCKED: a viewId present in two subscriptions on the same url is
delivered once PER subscription, so that url receives the same event twice. Check
getWebhookSubscriptions before reusing a url, and prefer editing the existing
subscription's viewIds over creating an overlapping one.
SIGNING SECRET (store it now — shown ONCE): the response `secret` (whsec_...) is returned in
plaintext ONLY here and from rotateWebhookSubscriptionSecret. It is stored encrypted and NEVER
returned again. Relay it to the user immediately and instruct them to save it; there is no way to
recover it later (they would have to rotate).
TIER LIMITS: the workspace's plan caps the number of subscriptions and views-per-subscription
(a violation returns a userError whose message states the limit and current usage).
OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status } | null,
secret: string | null, userErrors: [{ field, message }] }.
`webhookSubscription` and `secret` are both null on a userErrors failure. `userErrors[].field` is
an array of path segments (e.g. ["input","url"], ["input","viewIds"]).
VERIFY AFTER WRITE: confirm the subscription via getWebhookSubscriptions
(the WebhookSubscription type never re-exposes the secret).
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its 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 - Delete collectiondeletecollection · PURPOSE: Delete a Collection (a saved tag set) from the current workspace. This is a HARD,
IRREVERSIBLE delete.
WHAT IS DESTROYED: the Collection itself — its tag set and every tag association, so the tag is
stripped from every item it was applied to and can no longer be used as a filter.
WHAT SURVIVES: the ITEMS ARE NEVER DELETED — they simply lose this one tag; every other tag,
field, and piece of data on them is untouched.
INPUT:
- id (required string): the Collection's id, as returned by getCollections /
createCollection for this workspace. Resolve ids with getCollections first — NEVER guess a
Collection id.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the Collection with
getCollection(id:) and CONFIRM WITH THE USER, echoing the Collection's name back to them, before
you call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedCollectionId: null` — deleting an
already-deleted Collection is a safe no-op (idempotent). Same observable for unknown vs
cross-workspace, on purpose (no existence leak).
AVAILABILITY: Collection management is available only in workspaces whose Collections use the
standard storage. In a workspace that stores Collections in an older format, the ids
getCollections returns are not accepted here and the call returns a userError without
changing anything.
OUTPUT: { deletedCollectionId: string | null, userErrors: [{ field, message }] }.
`deletedCollectionId` echoes the deleted id on success, or is null when the Collection was not
found. `userErrors[].field` is an array of path segments (e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal with getCollection(id:) — it returns null once the
Collection is gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Delete content viewdeletecontentview · PURPOSE: Delete a saved content (media deck) view from the current workspace. This is a HARD,
IRREVERSIBLE delete.
WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears
from the workspace's saved views.
WHAT SURVIVES: the underlying CONTENT IS NEVER DELETED — the items the view listed remain in the
workspace untouched; only this saved filter configuration is removed.
INPUT:
- id (required string): the view's canonical FilterPreset UUID (as returned by
getContentViews / createContentView). Resolve ids with getContentViews first — NEVER guess.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with
getContentView(id:) and CONFIRM WITH THE USER, echoing the view's name back to them, before you
call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedContentViewId: null` — deleting an
already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace,
on purpose (no existence leak).
OUTPUT: { deletedContentViewId: string | null, userErrors: [{ field, message }] }.
`deletedContentViewId` echoes the deleted id on success, or is null when the view was not
found. `userErrors[].field` is an array of path segments (e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal with getContentView(id:) — it returns null once the view is
gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Delete creator viewdeletecreatorview · PURPOSE: Delete a saved Creator View from the current workspace. This is a HARD, IRREVERSIBLE
delete.
WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears
from the workspace's saved views.
WHAT SURVIVES: the underlying CREATORS ARE NEVER DELETED — the creators the view listed remain
in the workspace untouched; only this saved filter configuration is removed.
INPUT:
- id (required string): the view's canonical FilterPreset UUID (as returned by getCreatorViews
/ createCreatorView). Resolve ids with getCreatorViews first — NEVER guess.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with getCreatorView(id:)
and CONFIRM WITH THE USER, echoing the view's name back to them, before you call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedCreatorViewId: null` — deleting an
already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace,
on purpose (no existence leak).
OUTPUT: { deletedCreatorViewId: string | null, userErrors: [{ field, message }] }.
`deletedCreatorViewId` echoes the deleted id on success, or is null when the view was not
found. `userErrors[].field` is an array of path segments (e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal with getCreatorView(id:) — it returns null once the view is
gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Delete social profile viewdeletesocialprofileview · PURPOSE: Delete a saved Social Profile view from the current workspace. This is a HARD,
IRREVERSIBLE delete.
WHAT IS DESTROYED: the saved view itself — its stored filters, sort, and settings. It disappears
from the workspace's saved views.
WHAT SURVIVES: the underlying SOCIAL PROFILES ARE NEVER DELETED — the profiles the view listed
remain in the workspace untouched; only this saved filter configuration is removed.
INPUT:
- id (required string): the view's canonical FilterPreset UUID (as returned by
getSocialProfileViews / createSocialProfileView). Resolve ids with getSocialProfileViews
first — NEVER guess.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the view with
getSocialProfileView(id:) and CONFIRM WITH THE USER, echoing the view's name back to them, before
you call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedSocialProfileViewId: null` — deleting an
already-deleted view is a safe no-op (idempotent). Same observable for unknown vs cross-workspace,
on purpose (no existence leak).
OUTPUT: { deletedSocialProfileViewId: string | null, userErrors: [{ field, message }] }.
`deletedSocialProfileViewId` echoes the deleted id on success, or is null when the view was not
found. `userErrors[].field` is an array of path segments (e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal with getSocialProfileView(id:) — it returns null once the
view is gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Delete view groupdeleteviewgroup · PURPOSE: Delete a view group from the current workspace. This is a HARD, IRREVERSIBLE delete.
WHAT IS DESTROYED: the view group itself — the sidebar bucket and its membership rows. It
disappears from the workspace's groups.
WHAT SURVIVES: the MEMBER VIEWS ARE NEVER DELETED — every Content View and Social Profile View
that was in the group simply becomes ungrouped. Their FilterPreset UUIDs are returned as
`movedViewIds` so you can re-file them into another group if you want.
INPUT:
- id (required string): the view group's UUID (as returned by getViewGroups /
createViewGroup). Resolve ids with getViewGroups first — NEVER guess a group id.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first fetch the group with
getViewGroup(id:) and CONFIRM WITH THE USER, echoing the group's name back to them, before you
call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedViewGroupId: null` and `movedViewIds: []` —
deleting an already-deleted group is a safe no-op (idempotent). Same observable for unknown vs
cross-workspace, on purpose (no existence leak).
OUTPUT: { deletedViewGroupId: string | null, movedViewIds: [string], userErrors: [{ field, message }] }.
`deletedViewGroupId` echoes the deleted id on success, or is null when the group was not found.
`movedViewIds` lists the FilterPreset UUIDs of the views that were ungrouped (empty on
not-found or when the group had no members) — invalidate any cached ContentView /
SocialProfileView rows in this list. `userErrors[].field` is an array of path segments
(e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal with getViewGroup(id:) — it returns null once the group is
gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Delete webhook subscriptiondeletewebhooksubscription · PURPOSE: Delete a webhook subscription from the current workspace. This is a HARD, IRREVERSIBLE
delete.
WHAT IS DESTROYED: the subscription itself AND all of its delivery records (the deliveries cascade).
The endpoint stops receiving any further events immediately.
WHAT SURVIVES: the content views the subscription pointed at are NEVER deleted — only this
subscription (and its delivery history) is removed.
INPUT:
- id (required string): the subscription's UUID (as returned by getWebhookSubscriptions). Resolve
ids with getWebhookSubscriptions first — NEVER guess.
CONFIRM BEFORE CALLING: because this is IRREVERSIBLE, first look up the subscription and CONFIRM
WITH THE USER, echoing the subscription's name/url back to them, before you call this tool.
FAILURES: an unknown id, an already-deleted id, or an id owned by another workspace returns a
not_found userError (field ["id"]) with `deletedWebhookSubscriptionId: null` — deleting an
already-deleted subscription is a safe no-op (idempotent). Same observable for unknown vs
cross-workspace, on purpose (no existence leak).
OUTPUT: { deletedWebhookSubscriptionId: string | null, userErrors: [{ field, message }] }.
`deletedWebhookSubscriptionId` echoes the deleted id on success, or is null when the subscription
was not found. `userErrors[].field` is an array of path segments (e.g. ["id"]).
VERIFY AFTER DELETE: confirm removal by calling getWebhookSubscriptions — the id is gone.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Enable webhook subscriptionenablewebhooksubscription · PURPOSE: Re-enable a webhook subscription in the current workspace that was AUTO-DISABLED by
repeated delivery failures (status DISABLED_BY_FAILURES). Clears the failure counters and, by
default, replays the last 24h of failed deliveries one-shot per row (one more attempt each,
lifetime attemptCount preserved, no backoff-ladder reset) — a bounded catch-up, never a retry storm.
INPUT:
- id (required string): the failure-disabled subscription's UUID. Resolve it via
getWebhookSubscriptions first (look for status DISABLED_BY_FAILURES) — never guess.
- replayFailedSince24h (optional boolean, default true): when true, replays each failed
delivery from the last 24h once. Pass false to re-enable WITHOUT replaying the backlog.
ONLY DISABLED_BY_FAILURES SUBSCRIPTIONS ARE ENABLEABLE: a subscription in any other status
(active / disabled_by_user) returns a not_disabled_by_failures userError (field ["id"]) and
changes nothing. An already-enabled subscription is no longer DISABLED_BY_FAILURES, so re-calling
is a safe no-op not_disabled_by_failures (this is what makes the tool idempotent-safe). To resume
a user-paused (DISABLED_BY_USER) subscription, use updateWebhookSubscription with status ACTIVE.
FAILURES: an unknown id, or a subscription owned by another workspace, returns a not_found
userError (field ["id"]) — same observable on purpose (no cross-workspace existence leak).
OUTPUT: { webhookSubscription: { id, name, url, viewIds, eventTypes, metadata, status,
consecutiveFailures, lastSuccessAt, disabledAt } | null,
userErrors: [{ field, message }] }.
On success `webhookSubscription.status` is `ACTIVE` and `consecutiveFailures` is 0.
`webhookSubscription` is null on a not_found / not_disabled_by_failures failure.
`userErrors[].field` is an array of path segments.
VERIFY AFTER WRITE: confirm by calling getWebhookSubscriptions (status back to ACTIVE); if you
replayed, poll getWebhookDeliveries (filter by subscriptionId) for the catch-up attempts settling.
WRITE NOTES (apply to every write tool):
- userErrors mean the write DID NOT happen: a non-empty `userErrors` array is a SUCCESSFUL
tool result reporting a domain rejection, NOT a crash. The write did not take effect. Read
each entry's `field` (an array of path segments) and `message`, correct the input, and
either retry or ask the user — do not treat it as a transport error.
- Verify after write: after a success, confirm the change with the READ tool this tool's own
description names (e.g. a get*/search* tool) before reporting success to the user. Deletes
are confirmed when that read returns null.
- Retry discipline on a timeout / transport error (you cannot tell if the write landed):
tools annotated `idempotentHint: true` MAY be retried directly WITHOUT a verification read
— a duplicate apply converges to the same state. For every OTHER (non-idempotent) write,
READ FIRST with the verify tool above to check whether it already took effect BEFORE
retrying. NEVER blind-retry create* / uploadItemFromUrl / refetchEngagementBulk — a
duplicate creates a second entity, a second import, or re-spends credits.
- Destructive tools (`destructiveHint: true`) CHANGE OR REMOVE data that already exists, or
trigger a real delivery attempt — they are not purely additive. Say what will change before
you call one. Being destructive does NOT on its own require a confirmation round-trip; only
the IRREVERSIBLE tools named below do.
- IRREVERSIBLE tools — every delete*, plus rotateWebhookSubscriptionSecret (the previous
signing secret is gone for good) and refetchEngagementBulk (credits 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 - Get collectiongetcollection · PURPOSE: Look up a single Collection (saved tag set) by id in the current workspace.
INPUT: id (required string — a Collection id as returned by getCollections /
createCollection for this workspace). Ids differ between workspaces depending on
how each one stores Collections, so always resolve the id through getCollections
rather than constructing or reusing one.
OUTPUT: { collection: { id, name, itemCount } | null }.
Returns { collection: null } (NOT an error) when no Collection with that id
exists in the current workspace (unknown id, or id owned by another workspace —
same observable, on purpose). `itemCount` is the number of items tagged with the
Collection in this workspace.
In workspaces whose Collections are preset-backed you can pair the returned `id` with
searchItems(presetId:) to fetch the items tagged with the Collection; that pairing does
not apply where the workspace uses the older storage. Creating/renaming/deleting a
Collection (createCollection / updateCollection / deleteCollection) is available only in
workspaces whose Collections use the standard storage — where the workspace uses the
older storage those calls return a userError and change nothing. Tagging items with
addItemToCollections / removeItemFromCollections works in both.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get competitor brandgetcompetitorbrand · PURPOSE: Look up a single Competitor Insights brand by id in the current workspace, with
aggregate metrics over a period/date window. Also resolves the workspace's own
brand by its stable id (isOwnBrand: true).
INPUT: id (required string, the brand's Archive id), optional period ("WEEK"|"MONTH",
default "MONTH") and date (ISO date, defaults to today).
OUTPUT: { id, name, isOwnBrand, earnedMediaValue, engagementsTotal, impressions, reach,
postsCount, influencersCount, ownMetricsStatus } on success. For the own entry,
ownMetricsStatus is AVAILABLE (null metrics then mean no data in the window) or
UNAVAILABLE (retry later); it is null for competitors. Returns an isError tool
result with the text "Competitor brand not found" when no brand with that id is
tracked in the current workspace (unknown id, or known id tracked only by another
workspace — same observable, on purpose).
WARNINGS: If the metrics store is temporarily unavailable, brand identity data is still
returned with null metrics and a `warnings` array containing the message below.
An own-brand result has ownMetricsStatus "UNAVAILABLE". Back off for minutes
before retrying; do not immediately repeat the call.
"Competitor metrics are temporarily unavailable. Retry later."
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get content viewgetcontentview · PURPOSE: Look up a single saved content (media deck) view by id in the current workspace.
INPUT: id (required string, the content view's Archive id / FilterPreset UUID).
OUTPUT: { contentView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }.
Returns { contentView: null } (NOT an error) when no media_deck view with that id
exists in the current workspace (unknown id, or id owned by another workspace —
same observable, on purpose).
Pair the returned `id` with searchItems(presetId:) to fetch the items belonging to
the view.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get creatorgetcreator · PURPOSE: Look up a single creator (influencer) by id in the current workspace.
INPUT: id (required string, the creator's Archive id).
OUTPUT: { id, customAttributes } on success. Returns an isError tool
result with the text "Creator not found" when no creator with
that id exists in the current workspace (unknown id, or known
id owned by another workspace — same observable, on purpose).
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get creator viewgetcreatorview · PURPOSE: Look up a single saved Creator View by id in the current workspace.
INPUT: id (required string — the Creator View's Archive id / FilterPreset UUID,
as returned by getCreatorViews).
OUTPUT: { creatorView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }.
Returns { creatorView: null } (NOT an error) when no creator view
with that id exists in the current workspace (unknown id, or id owned by another
workspace — same observable, on purpose).
Pair the returned `id` with searchCreators(presetId:) to fetch the view's creators.
To create/update/delete use createCreatorView / updateCreatorView / deleteCreatorView.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get custom attribute schemasgetcustomattributeschemas · PURPOSE: List custom attribute schemas for the current workspace,
used both to INTERPRET values in the `customAttributes` JSON
field on items/creators AND to BUILD `customAttributeConditions`
filters for searchItems / searchCreators.
INPUT: entity (required: "ITEM" or "CREATOR"). ITEM → schemas attached
to items (`shop_item`); CREATOR → schemas attached to creators.
OUTPUT: { items: [{ key, name, type, aiGenerated, options: [{ id, name }] }] }
- `key` matches a top-level field inside the entity's
customAttributes hash (and is the `field` in a filter condition).
- `type` is the upper-case attribute type (TEXT, SINGLE_SELECT_V2,
MULTIPLE_SELECT_V2, etc.) — pass it as `type` in a filter condition.
- `aiGenerated` is true when the attribute's value is produced by an
AI Filter; searchItems returns the model's explanation for those
keys in each item's `aiFilterReasons`.
- `options` is `[]` for non-select schemas; for select-type
schemas each option's `id` is what appears as the value
(or array element) in customAttributes and is the `value`
you pass when filtering on that field.
CUSTOM ATTRIBUTE FILTERS (customAttributeConditions):
Filter by the workspace's user-defined custom fields. An array of
{ field, operator, type, value } objects; multiple entries are AND-ed together.
Step 1 - discover fields: call getCustomAttributeSchemas(entity: ITEM | CREATOR).
Each schema returns `key` (use as `field`), `type` (use as `type`), and
`options: [{ id, name }]` for select fields (use an option `id` as `value`).
Step 2 - build each condition:
- field : the schema `key` (e.g. "sentiment", "links").
- type : the schema `type`, UPPERCASE - one of TEXT, EMAIL, PHONE, URL, NUMBER,
BOOLEAN, DATE, DATETIME, SINGLE_SELECT_V2, SINGLE_SELECT_V3,
MULTIPLE_SELECT, MULTIPLE_SELECT_V2, TEXT_LIST, NUMBER_LIST, DATE_LIST,
DATETIME_LIST, BOOLEAN_LIST.
- operator : UPPERCASE; the valid set depends on the field's type group (see below).
- value : depends on `type`:
* TEXT / EMAIL / PHONE / URL -> a string.
* SINGLE_SELECT_V2 / SINGLE_SELECT_V3 -> the chosen option `id` (UUID).
* NUMBER -> a number; BETWEEN takes { from, to }.
* DATE / DATETIME -> ISO-8601 string; BETWEEN takes { from, to }.
* BOOLEAN -> true / false.
* MULTIPLE_SELECT / MULTIPLE_SELECT_V2 -> array of 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 - Get engagement historygetengagementhistory · PURPOSE: Paginated history of engagement metric snapshots for a single
item, newest first.
INPUT: itemId (required string). Optional cursor, limit (default 20,
max 100), and filter { capturedAt: { from, to } } (ISO-8601
date-time bounds, UTC only — offsets other than Z/+00:00/-00:00
are rejected; both bounds are optional).
An unknown or foreign itemId returns an isError result with the
coded text "Item not found".
OUTPUT: { items: [...], pageInfo: { hasNextPage, endCursor }, totalCount }.
Each item node is { at, likes, comments, views, shares,
impressions, earnedMediaValue, followers, linearViralityScore }.
Metric values in nodes may be null when data has not been captured.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get media contentsgetmediacontents · PURPOSE: Retrieve media contents (images / videos) for shop items OR for tracked
Competitor Insights items in the current workspace.
INPUT: exactly one of itemIds (Item / shop-item UUIDs) OR competitorBrandItemIds
(CompetitorBrandItem UUIDs from getCompetitorBrandItems). Each is an array of
1..100 string IDs. Passing both, or neither, returns an isError result.
OUTPUT: { items: [...] } — one node per media-content row. Each node carries
id, mediaItemId, type, fileUrl, thumbnailUrl, width, height, deleted; videos
additionally carry videoDuration.
NOTE: fileUrl and thumbnailUrl are raw CDN URLs (unsigned); YouTube nodes always have
fileUrl: null and thumbnailUrl may be null. Unknown, cross-workspace, or
non-entitled competitor item IDs yield zero rows (not an error).
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get operationgetoperation · PURPOSE: Look up a single async operation by id in the current workspace —
poll its status and progress (e.g. after refetchEngagementBulk).
INPUT: id (required string, the operation's Archive id).
OUTPUT: { id, status, operationType, total, processed, succeededCount,
failedCount, pendingCount, succeededItemIds, failedItemIds,
pendingItemIds, itemIdsTruncated, createdAt, completedAt }
on success (completedAt is null until the operation finishes).
The three counts are always exact. Each item-id list holds at most
1000 ids: when itemIdsTruncated is true some ids were
omitted — use the counts for exact sizes and page through
getOperationRecords for the remaining ids; do not report a partial
list as the whole outcome.
Counts and id lists are separate reads: while status is QUEUED or
PROCESSING they are a moving snapshot and may briefly disagree as
records finish; on a terminal status records no longer change, so
the response is exact and self-consistent.
status values: QUEUED (not yet started), PROCESSING (in progress),
COMPLETED (all items succeeded — terminal), PARTIAL (finished with
some failures — terminal), FAILED (all items failed — terminal).
Returns an isError tool result with the text "Operation not found" when
no operation with that id exists in the current workspace (unknown id,
or known id owned by another workspace — same observable, on purpose).
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 - Get operation recordsgetoperationrecords · PURPOSE: Page through the per-item records of one operation, newest first —
the follow-up to getOperation when itemIdsTruncated is true, and the
way to enumerate an operation's items with their per-item status.
INPUT: operationId (required string), optional status filter
(SUCCEEDED | FAILED | PENDING — PENDING covers not-started,
in-progress and retrying records), optional cursor, limit
(default 100, max 1000).
OUTPUT: { records: [...], pageInfo: { hasNextPage, endCursor }, totalCount }.
Each record is { itemId, status }; itemId is a documented input to
getMediaContents / getTranscriptions. totalCount is the exact number
of records matching the filter.
Returns an isError tool result with the text "Operation not found" when
no operation with that id exists in the current workspace, and
"Invalid cursor" for a cursor this connection did not issue.
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 - Get social profilegetsocialprofile · PURPOSE: Look up ONE social profile (influencer) in the current workspace. For MANY
profiles do NOT loop this per id — call getSocialProfiles once to list/filter a
whole page of profiles in a single call (the batch path).
INPUT: provide EXACTLY ONE of:
- id (the social profile's Archive id), OR
- accountName + provider (provider is one of instagram, tiktok, youtube).
Optional fallback (default false): when true and the profile is not archived
locally, fetch it live from the social platform (slower). Passing both id and
accountName/provider, or accountName without provider, or neither, returns an
isError result.
OUTPUT: { socialProfile: { id, provider, originalUrl, accountName, avatar, private,
verified, followers, following, fullName, email, phoneNumbers, proAccount,
creator { id } } } on success, or { socialProfile: null } when no profile
matches in the current workspace (workspace-scoped lookup; profiles archived
only in other workspaces are treated as misses and return null, not an error).
With fallback: true a miss may instead return isError: true with one of:
- "Upstream social-platform service is temporarily unavailable. Please retry."
(UPSTREAM_UNAVAILABLE code in extensions — safe to retry);
- a PROVIDER_ERROR message from the platform (e.g. rate-limited);
- "User not found on <provider>." (the account does not exist on the platform).
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get social profile viewgetsocialprofileview · PURPOSE: Look up a single saved Social Profile view by id in the current workspace.
INPUT: id (required string — the Social Profile view's Archive id / FilterPreset UUID,
as returned by getSocialProfileViews).
OUTPUT: { socialProfileView: { id, name, filters, customAttributeConditions, sort, showReportingStats, group } | null }.
Returns { socialProfileView: null } (NOT an error) when no social-profiles view
with that id exists in the current workspace (unknown id, or id owned by another
workspace — same observable, on purpose).
Pair the returned `id` with getSocialProfiles(presetId:) to fetch the view's social
profiles. To create/update/delete use createSocialProfileView / updateSocialProfileView /
deleteSocialProfileView.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get transcriptionsgettranscriptions · PURPOSE: Fetch transcriptions for the media contents of one or more shop items
in the current workspace.
INPUT: itemIds (required) — an array of 1..1000 Item / shop-item UUIDs.
OUTPUT: { items: [...] } — one node per media content that has a non-empty
transcript across the requested items. Each node carries mediaContentId
and transcript. Items with no (or only empty) transcriptions yield no rows.
NOTE: v0 exposes only the itemIds (Elasticsearch) path; the deferred
mediaContentIds (DB) path is not available.
NOTE: rate-limit cost scales with the number of itemIds but is capped
at 100 — a request with more than 100 ids (up to the 1000 max) is
charged as 100.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get view groupgetviewgroup · PURPOSE: Look up a single user-created view group by id in the current workspace,
including its member views.
INPUT: id (required string — the ViewGroup UUID as returned by getViewGroups /
createViewGroup).
OUTPUT: { viewGroup: { id, name, contentViews: [{ id, name }], socialProfileViews: [{ id, name }],
creatorViews: [{ id, name }] } | null }.
Member views carry id and name only — fetch a member's configuration (filters, sort, …)
with getContentView / getSocialProfileView / getCreatorView.
`contentViews`, `socialProfileViews`, and `creatorViews` enumerate the group's member views in
their within-group position order (ties break on id for a stable order). CAMPAIGN
Creator Views are never group members (they are campaign-scoped, not
workspace-scoped) — workspace Creator Views ARE groupable and appear in `creatorViews`.
Returns { viewGroup: null } (NOT an error) when no group with that id exists in
the current workspace (unknown id, or id owned by another workspace — same
observable, on purpose).
To create/rename/delete a group use createViewGroup / updateViewGroup / deleteViewGroup;
to move a view into/out of a group use moveContentViewToGroup / moveSocialProfileViewToGroup /
moveCreatorViewToGroup.
reorderViewsInGroup rewrites member positions — read the full member list from this tool
first, since a reorder that omits members leaves their positions unchanged.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - Get workspacegetworkspace · PURPOSE: Get a workspace — its tracked hashtags/mentions/keywords and connected
social integrations — in one call. The workspace is selected by the
optional `workspaceId` argument (falling back to the `WORKSPACE-ID`
header, or your sole workspace when you can access exactly one). Call
getWorkspaces first to discover the ids you can access.
INPUT: optional workspaceId — the workspace UUID (from getWorkspaces) or the
numeric workspace id shown in the Archive app.
OUTPUT: { workspace: { id, name, hashtags: [...], mentions: [...],
keywords: [...], integrations: [...] } }.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - List campaignsgetcampaigns · PURPOSE: List campaigns for the current workspace, newest first.
INPUT: optional cursor, limit (default 20, max 100). No filter, no sorting
(results are always newest first).
OUTPUT: { campaigns: [...], pageInfo: { hasNextPage, endCursor }, totalCount }.
Each campaign node is { id, name, createdAt }.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - List collectionsgetcollections · PURPOSE: List Collections (saved tag sets) for the current workspace, in the workspace's
saved display order.
INPUT: none (beyond the workspace selector).
OUTPUT: { items: [{ id, name, itemCount }] }
`id` is the id this workspace accepts for addItemToCollections /
removeItemFromCollections and for getCollection(id:). ALWAYS take Collection ids
from this tool — never construct them, cache them, or reuse one across
workspaces. Some workspaces store Collections in an older format and their ids
look different; the ids listed here are always the ones that workspace accepts.
`itemCount` is the number of items in this workspace tagged with the Collection.
In workspaces whose Collections are preset-backed you can pair a Collection's `id` with
searchItems(presetId:) to fetch the items tagged with that Collection; that pairing does
not apply where the workspace uses the older storage. Creating/renaming/deleting a
Collection (createCollection / updateCollection / deleteCollection) is available only in
workspaces whose Collections use the standard storage — where the workspace uses the
older storage those calls return a userError and change nothing. Tagging items with
addItemToCollections / removeItemFromCollections works in both.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - List competitor brand itemsgetcompetitorbranditems · PURPOSE: Paginate posts attributed to a single tracked Competitor Insights brand
within a required `takenAt` time window.
INPUT: brandId (required string, the brand's Archive id — not an ISO-8601 date-time);
takenAtFrom and takenAtTo (required ISO-8601 date-time, UTC only — offsets
other than Z/+00:00/-00:00 are rejected);
optional sorting ({ sortKey: TAKEN_AT|EARNED_MEDIA_VALUE, sortOrder: ASC|DESC },
default TAKEN_AT DESC), limit (default 20, max 100), cursor,
responseFormat (concise | detailed, default detailed — see OUTPUT).
An unknown or foreign brandId returns an empty connection (not an error).
EARNED_MEDIA_VALUE ranks posts as one ordering across the whole window.
OUTPUT: { items: [...], pageInfo: { hasNextPage, endCursor }, totalCount }.
responseFormat "detailed" (default) returns the full per-node shape; "concise"
trims each node to id, takenAt, provider, type, originalUrl, caption,
socialProfile { id accountName }, and currentEngagement — for listing /
filtering / ranking.
NOTE: a month's posts become available on day 9 of the following month — the
current month is never queryable. The cursor is positional — pass
pageInfo.endCursor back with the same brandId, takenAtFrom/takenAtTo, and
sorting; responseFormat may change freely between pages. A malformed
cursor (including one minted before the ClickHouse cutover) is rejected
with "Invalid cursor", and pagination depth is capped at 10,000 posts
per query — narrow the takenAt window to go deeper.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - List competitor brandsgetcompetitorbrands · PURPOSE: List Competitor Insights brands tracked for the current workspace, newest first,
with aggregate metrics over a period/date window, so self-vs-competitor Share of
Voice is computable in one call.
INPUT: optional cursor, limit (default 20, max 100), period ("WEEK"|"MONTH", default
"MONTH"), date (ISO date, defaults to today). No filter, no sorting (results are
always newest first).
OUTPUT: { brands: [...], pageInfo: { hasNextPage, endCursor }, totalCount }.
Each brand node is { id, name, isOwnBrand, earnedMediaValue, engagementsTotal,
impressions, reach, postsCount, influencersCount, ownMetricsStatus }. The
workspace's own brand is returned as one additional pinned node on the first page
(isOwnBrand: true, name "You"), in addition to up to `limit` competitor brands;
totalCount counts competitor brands only. For the own entry, ownMetricsStatus is
AVAILABLE (null metrics then mean no data in the window) or UNAVAILABLE (retry
later); it is null for competitors.
WARNINGS: If the metrics store is temporarily unavailable, the roster is still returned
with null metrics, ownMetricsStatus "UNAVAILABLE", and a `warnings` array
containing the message below. Back off for minutes before retrying; do not
immediately repeat the call.
"Competitor metrics are temporarily unavailable. Retry later."
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing - List content viewsgetcontentviews · PURPOSE: List saved content (media deck) views for the current workspace,
ordered most-recently-updated first.
INPUT: optional groupId (filters to one group; omitted/null returns all media_deck views).
An unknown or foreign groupId returns an empty list (not an error).
OUTPUT: { items: [{ id, name, filters, customAttributeConditions, sort, showReportingStats, group }] }
Pair an item's `id` with searchItems(presetId:) to fetch the items belonging to a
view.
RUNTIME NOTES:
- Rate limiting: a weighted per-workspace rate limit is enforced on every call.
A throttled call returns isError text: "Rate limit exceeded. Retry after N seconds."
- Argument validation: arguments failing schema validation return isError text like
"Invalid argument '$.<path>': <reason>".
- Unexpected failures: any other error returns isError text "Tool execution failed".ArchiveArchive
PluginrequiredMarketing
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.