create-attachment-from-upload
ChatGPTAttach a previously uploaded file to a CRM record. When to use this tool Use this after prepare-attachment-upload returned an upload URL and you have uploaded the file bytes to it. This finalizes the attachment so it appears in the record's Attachments section. How it works Pass the record entity and id, plus the key and attachment_id returned by prepare-attachment-upload, and the file name. The file must already be uploaded to the URL from that step; this tool does not transfer bytes. Use get-records first if you need the target record's id.
create-campaign
ChatGPTCreate a new email campaign (sequence) in DRAFT mode. To edit an existing campaign, use the update-campaign tool instead. 📖 For comprehensive campaign rules and examples: Use read-context with context: "campaign-docs" When to use this tool - When the user asks to create a new email campaign or sequence - When duplicating a campaign (first fetch it with get-campaigns, then call this tool with the email steps) Structuring the campaign Structure email steps with subjects, bodies (HTML), and timing. Campaigns start in DRAFT mode. When drafting campaign emails: - Keep subject lines under 50 characters, conversational, not overly formal - Use a warm, conversational tone while remaining professional - Be concise and include a clear call to action if appropriate - Start with a friendly greeting and end with a warm closing Essential rules 1. Email/Delay Pattern: Emails and delays MUST alternate. Always specify delay_after_days on each step 2. First email: delay_after_days: 0. Subsequent emails: minimum 1 day 3. Campaigns are Workflows: Use this tool for campaigns, not generic workflow tools 4. Campaign emails are templates sent to multiple people 5. Use variable placeholders: {{path||fallback}} with fallback, or {{path}} without fallback 6. Only use the variables listed below (do not invent variables) Threading: send_as_reply Set send_as_reply: true on a step to thread it as a reply under the previous step (uses the prior message id and "Re: <previous subject>"). Use it when a follow-up bumps the prior email and the recipient should see it inline: - Use send_as_reply: true for content like "circling back", "just bumping this", "wanted to follow up", or any step that explicitly references the prior email's call to action without a new pitch. - Leave it off (default) when the step introduces a new angle, case study, or call to action; recipients triage by subject, and a fresh subject signals fresh content. Rules: - Step 1 always starts a new thread (send_as_reply must be false / omitted). - When send_as_reply: true, the step's subject is ignored at send time (the previous step's subject is reused with "Re: " prepended), but you must still provide a sensible subject for storage. Fallback rules (fallbacks make emails feel natural when data is missing): - ALWAYS include fallbacks for human-identifiable information: → Person names (first_name, last_name, full_name) → use "there", "Friend", etc. → Company/organization names → use "your company", "your organization", "your team" → Job titles and roles → use "your role", etc. → Location/city names → use "your area", "your region", etc. - Skip fallbacks ONLY for technical/structured data: → URLs, email addresses, phone numbers → Dates, timestamps, IDs → Numerical values and metrics Examples <example> Create a campaign: { "campaign_name": "New Leads Nurture", "email_steps": [ { "subject": "Welcome, {{person.name.first_name||there}}", "body": "<p>Hi {{person.name.first_name||there}},</p><p>Thanks for your interest!</p>", "delay_after_days": 0 }, { "subject": "Resources for {{person.company_id.name||your team}}", "body": "<p>Hey {{person.name.first_name||there}},</p><p>Here are some helpful resources...</p>", "delay_after_days": 3 } ] } </example> Important notes - Audience is optional for drafts. Set list_id for a segment, or recipient_ids for specific people the user names. Never create a list just to hold specific people - Email account connection is optional for drafts (required before activating) - Campaigns are created in DRAFT mode (enabled: false) - delay_after_days: 0 = immediate, 3 = day 3, 7 = day 7 - Minimum 1 day enforced between consecutive emails automatically - Body should be formatted as HTML. Put each paragraph in its own <p> tag; paragraphs render with a blank line between them. Use <br/> only for a hard line break within a paragraph (e.g. between signature lines) - When describing timing to users, say "immediately" for delay_after_days = 0, "on day 3" for delay_after_days = 3
create-email-draft
ChatGPTcreate-list
ChatGPTCreate a new list (saved view) based on a SQL query. Use this after running a SQL query to save the results as a reusable list that the user can access later. The SQL query should already be validated and working. When to use this tool Use this after running a SQL query to save the results as a reusable list that the user can access later. Important notes - SQL queries must use explicit column names (e.g., "SELECT name, email FROM person") - SELECT * queries are not supported and will fail validation - Column names must be table-qualified (e.g., "person.name" not just "name") - The tool validates both the main query and count query to ensure the list will work when opened - Aggregate functions in ORDER BY or HAVING must also be in the SELECT clause After creating a list: 1. Tell the user what was created in a friendly, conversational way 2. Provide a clickable markdown link to open the list (the tool response will include this) 3. Encourage the user to click the link to view the list 4. Keep your response concise - the tool will provide the formatted link
create-or-update-agent
ChatGPTCreate or update an agent, an autonomous AI assistant. When to use this tool - When the user asks you to create or update an agent Before you build: is an agent the right tool? Run these checks before planning. If one fails, say so and propose the better path rather than building a degraded agent. 1. Can the platform do it? The triggers below are the only events an agent can respond to. Notably, there is no delay trigger: "do X three days after event Y" must be re-expressed as a recurring schedule that queries for records that have reached that state. Also confirm the request fits the cadence, delivery-channel, and data-access limits documented in the planning and trigger sections below. 2. Is an agent the right fit? An agent is right whenever a task needs to run automatically on a trigger and involves any reasoning, including per-record classification like "categorize as ICP" or "classify meeting type." One ask isn't an agent: "keep a collection of records matching criteria" is a dynamic list. Scope every agent tightly (see "Narrow the scope with filters" below): ask how the relevant records enter Clarify, and if they share a label or marker, suggest a dynamic list and an On add to list trigger so the agent runs only for that subset rather than every record. For enrichment, use find-leads/import-leads for net-new prospects and a filtered agent for existing records. Before creating a new agent Do not immediately create an agent when the user asks. First, have a brief conversation to understand their goal and tailor the agent to their needs. Ask the user clarifying questions. 1. Check for existing agents: Use get-agents to see what agents already exist in the workspace. This is a silent background check; if no agents exist, do not acknowledge it; just continue. If an agent already covers the requested use case, point the user to it instead of creating a duplicate. If one is close, mention it and ask whether they'd like to update the existing agent or create a separate one. You can only update or delete an agent you created. get-agents shows each agent's Owner (name and email); you own an agent when its Owner is you. Never pass the agentId of an agent owned by someone else; updating it will be rejected. When the only close match belongs to another person, create a new agent for this user instead of trying to update theirs. 2. Understand the use case: Ask what they want the agent to do, what triggers it, and what the expected outcome is. Dig into vague requirements, e.g., if the user says "categorize people as ICP", ask what their ICP definition is (unless the org description already defines it). If they say "update fields after meetings", ask which fields. 3. Narrow the scope, or batch on a schedule: When a trigger would fire on every record of a type (e.g., all person creates, all deal updates), confirm the user actually wants that broad scope. Each agent run uses resources, so reducing unnecessary runs matters. Two levers: - Filters (specific lists, field conditions, record types) keep the per-event trigger but skip runs that don't match. - A schedule trigger replaces many per-event runs with one recurring run that queries for the records needing work and processes them in a batch — far fewer runs. Prefer this only when the work tolerates a delay (up to the schedule interval) and the target records can be found by a query (they've reached some state), not when the agent must react immediately (e.g., alert the moment a deal is won) or needs the change event's before/after values. 4. Warn about data visibility: Agents run with the creating user's permissions. They can only see emails the user sent, received, or was CC'd on, and meetings the user is a participant of, unless teammates have shared access to meetings and emails via Settings. Tell the user this when creating agents that process emails or meetings, and suggest they ask teammates to share access via Settings if the agent needs broader visibility. 5. Suggest complementary agents: If the agent they want wou…
create-or-update-artifact
ChatGPTCreate a new artifact or write a new version to an existing artifact. An artifact is a named document with versioned content (e.g. a report, a plan, a draft) that the user can view and revisit over time. When to use this tool Pick the surface that fits the ask: - Answer inline in the chat reply for a single fact, number, or short answer. Do NOT create an artifact for a quick factual lookup. A qualifier like "quick" or "simple" on a report or dashboard ask describes the effort, not the format, so that ask still gets an artifact. - Create an artifact whenever the user wants a durable, revisitable deliverable: a report, dashboard, deck, analysis, breakdown, summary, overview, or plan, even when they never say the word "artifact". Reach for one especially for a multi-part deliverable, or for current-state / snapshot data. Artifacts open at their own URL and the user revisits them, so produce one instead of dumping the whole thing into the chat transcript. A deck, slides, a QBR, a pitch, or a readout is a slide deck built with @clarify/ui/deck (see "Building a deck"). - If the deliverable is really just a browsable set of records of a single object type (columns are direct fields, no aggregation or charts), a Clarify list via create-or-update-list fits better than an artifact. When the user asked for a report or analysis but a list would serve them, offer the list and explain the distinction first; if they explicitly asked for a list, just build it. Referring to it in chat When you mention the result to the user, name it by what it is — a dashboard, report, deck, sales document, or plan. Do not call it an "artifact" unless the user used that word first. Requirements and prerequisites Complete all four steps before you call this tool with new SQL. Every step is required. 1. Read the "artifact-docs" context with read-context, and complete every step it requires. It names any further context to read, because which ones exist depends on the workspace. 2. Run every query before you put it in content. Prove the independent ones together — emit them as one batch of parallel tool calls in a single turn, not one query per turn. Only a query that needs an earlier query's result stays serial. 3. State every definition you have not saved, and ask the user whether to save it. If the user does not answer, offer again in each reply until the user answers. 4. Call get-artifact-component-doc once. Pass every @clarify/ui/* component this version uses in one specifiers list. Call it every time, even for a component you used before in this conversation, or one you already know. Docs can change between calls, within one conversation or overnight. Do not trust memory from earlier in this conversation for current props. The one-line summaries below help you pick a component. They do not give its props. Never guess a stage name, a date field, or a metric definition. Never put a query in an artifact that you have not run. Never use a component's props from memory or from its one-line summary. Always call get-artifact-component-doc first. No exceptions. How it works Omit artifact_id to create a new artifact (name is required in this case). Pass an existing artifact_id to write a new version onto that artifact instead. Supply the new version in one of two ways — pass content or edits, never both: - content — the full JSX source. Use it to create an artifact, or to fully replace an existing version. - edits — a list of targeted { old_string, new_string } replacements applied to the currently-saved source on the server. Prefer this to update an existing artifact, especially a large one: you send only the changed regions, so the update is smaller and faster and never has to re-emit the whole file. Read the saved source first with get-artifact-source and copy each old_string exactly, whitespace included; each must match once, or set replace_all to replace every occurrence. When updating an artifact you did not just author in this conversation, first read its current source with get-artifact-source so you …
create-or-update-calendar-event
ChatGPTCreate a new calendar event or update an existing one. When to use this tool - To create a new event: omit event_id and provide title and when. - To update an existing event: provide event_id along with the fields to change. When not to use this tool - This writes to the user's external calendar (Google / Microsoft) and can email invites. If the user means a custom object (one of the entity options on create-or-update-records, e.g. an "Events" object), create a record there instead. Important notes - Only the fields you provide are updated; omitted fields remain unchanged. - For recurring events, updates apply to the whole series. - notify_participants defaults to true on create (invite goes out) and false on update (so minor edits don't spam attendees). Set it explicitly when rescheduling or making changes attendees should know about. - When updating participants, the default is add: the listed emails are merged into the existing attendee list. To remove attendees, set participants_mode to replace: anyone not listed will be removed. - Get the user's timezone from get-current-user and pass it explicitly via when.timezone for timed events. Don't guess. - The write happens as soon as this tool is called, with no confirmation step. Confirm with the user first.
create-or-update-fields
ChatGPTCreate new fields or update existing field metadata (name, options, AI config) on any entity (company, person, deal, meeting, task, or custom object). Does not support changing an existing field's type or nullable/required status. Also known as custom fields. Rename fields, add enum/select options (existing options are preserved; pass an empty options array to clear all), change option colors, and configure AI autofill. Supports all field types: text, number, currency, date, enum, multi-select, markdown, attachment, and relationships. You must use read-context with context="field-docs" before creating or updating fields to load field type guidelines, AI prompt writing instructions, and examples. Important notes - Field deletion is permanent. Clarify cannot restore or recover a deleted field. Never tell a user a deleted field can be brought back. - If a user asks to restore or undelete a field, explain it can't be recovered and offer to create a new field with that name instead: it will be a fresh, separate field. Board / Kanban views group by an existing field A board (Kanban) view groups records into columns by one specific enum field, usually the object's built-in status field, or the field configured for that board. Creating a new enum field (e.g. a custom stage) does NOT change which field the board groups by, and you cannot see a view's board-grouping configuration from here. - When a user wants records to move between board columns, set values on the field the board already groups by (commonly status), or ask the user which field the board should group by. Do not create a parallel new enum and tell the user the board will use it. - If records don't land in the expected column but the data reads back correctly, do not blame a display, refresh, or caching issue. The likely cause is that the board groups by a different field than the one you set.
create-or-update-list
ChatGPTCreate or update a dynamic list: a saved view whose membership is defined by a SQL query. Dynamic lists are the only list type Clarify supports today. When to use this tool - To create a new list: omit list_id and provide title + sql - To update an existing list: provide list_id along with the fields to change - When creating a new list, always set an emoji: a single emoji that represents the list's theme (e.g. "💰" for a deals list, "🎯" for priority accounts, "📅" for a meetings list). The emoji goes only in emoji; never prefix title with it, since the list already renders the emoji next to its title When the user is viewing a list and asks to show, filter, narrow, or sort what they see ("show me just X", "filter to Y", "without Z"), update that list: pass its list_id and the full SQL built from its current definition. The update is staged as a draft, so it does not change the live list for anyone until the user applies it. Create a new list (omit list_id) only when the user asks for a new or separate list. Only pass title with a list_id when the user explicitly asks to rename it. A list is the right surface when the user wants a browsable, filterable set of records of one object type. If they want aggregations, charts, or a multi-part deliverable, that is a report or dashboard (create-or-update-artifact), not a list. If the user explicitly asked for a list, build it without asking; if they asked for a report but the result is really just a list of records, offer the list and explain the distinction first. If the user asks for a "static list" or to manually curate a list Static lists are deprecated. Clarify no longer offers them as a list type, although some legacy static lists may still exist in the workspace. Do not call this tool to create one. Tell the user static lists are no longer supported, then offer the workaround: add a label to the records they want to track, and create a dynamic list that filters on that label. All built-in entities (companies, people, deals) have a built-in labels field with color-coded user-defined values, so the user can add or remove the label to control list membership manually. SQL restrictions This tool supports a restricted SQL subset. The query is used for both display and counting (via COUNT() wrapping), so certain patterns are incompatible: - SELECT : Use explicit, table-qualified column names (e.g., "SELECT person.name, person.email_addresses FROM person") - DISTINCT ON*: Not compatible with COUNT() wrapping. Use GROUP BY for deduplication instead. - CTEs (WITH ... AS): Not supported. Rewrite using JOINs or WHERE clauses. - Subqueries in FROM: SELECT ... FROM (subquery) is not supported. Query the table directly with WHERE/JOIN clauses. - LIMIT / OFFSET: Not supported. A dynamic list is a continuously-evaluated membership filter, not a ranked snapshot; the LIMIT is dropped during evaluation, so a "top N" query silently matches every qualifying record. To bound a list, narrow membership with a WHERE clause (e.g. a recency window like "meeting.start > NOW() - INTERVAL '30 days'") or have the user add a label and filter on it. ORDER BY is fine; it sets the default sort, not membership. - Column names must be table-qualified (e.g., "person.name" not just "name") - Aggregate functions in ORDER BY or HAVING must also appear in the SELECT clause - Filtering labels / multi-select fields: these store JSONB as {items: string[]}. Filter membership with the array-overlap operator, e.g. (person.labels -> 'items') ?| ARRAY['BDR Prospect']. Do not use the @> containment operator; it works in the database but the list UI cannot render or edit a filter built with it. - Combining AND and OR: Wrap each OR (or AND) group in parentheses, e.g. WHERE company.industry = 'SaaS' AND (company.city = 'Boston' OR company.city = 'Austin'). Never mix AND and OR at one level without parentheses: a OR b AND c runs as a OR (b AND c) and the list UI cannot edit it. The list UI edits one level of groups only: each group holds two or more plain conditions join…
create-or-update-object
ChatGPTCreate a new custom object type or update an existing one in the workspace. When to use this tool - When the user asks to create a new custom object type - When the user asks to update the name, plural label, description, or avatar (icon and background color) of an existing custom object How it works - Without entity: creates a new custom object (name, plural, and description are required) - With entity: updates the specified custom object's metadata, including its avatar icon and background color Recommended workflow for create 1. Discuss with the user what the custom object represents 2. Ask for the object name and suggest 2-3 plausible names based on the conversation (e.g., "Contract", "Ticket", "Project"). Confirm plural and description with the user as well. 3. Call this tool to create the object 4. Immediately call create-or-update-fields with the returned entity identifier to add fields Description guidance The description field is stored as AI context for this object type, directly influencing how AI understands and works with records of this type. A good description captures what business concept this object represents and how the team uses it. Before creating a new object, if the user has not provided a description, ask them for one. Work with them to craft a description that captures the object's purpose. Only proceed without a description if the user explicitly declines.
create-or-update-records
ChatGPTCreate or update records in Clarify. Supports bulk operations of up to 25 records per call. - Without id: Create a new record - With id: Update an existing record by ID This tool also links and unlinks relationships between records, not just attributes -- see the Relationships section below for trigger words and examples. All records in a single call must be the same operation: either all creates (no id) or all updates (with id). Split mixed batches into two separate calls. Each call is atomic: if any record in the batch fails, the entire batch is rolled back and no records are written. When working with user-supplied IDs (CSVs, pasted lists) verify the IDs exist with query-data first, or use smaller batches to limit the blast radius of a single bad ID. Before creating or updating records - Writable fields are not included in this tool's description. You must call get-schema with format "write" and only the entities you need before creating or updating records, unless the write schema for those entities was already loaded earlier in this conversation. Do not guess field names. - Never tell a user a field can be set before confirming it appears in the write schema. Computed / read-only fields (system-managed, e.g. interaction dates) are deliberately omitted from the write schema: they cannot be set here, and a field missing from the write schema is read-only or nonexistent, not something to recreate as a custom field. Do not offer to set such a field or to create a custom field of the same name. Explain that it updates automatically (from meetings and emails) and offer the real workaround, such as logging a meeting. - Before creating a company, deal, or person, use the query-data tool to search for existing records by name (ILIKE). Use a broad search, not a narrow ID filter. - If the search returns potential matches, present them to the user and ask whether to use an existing record or create a new one. - If no matches are found, proceed to create without asking for confirmation. - When creating a meeting, provide title (string), start (ISO 8601 datetime), and end (ISO 8601 datetime) in attributes. Set attendees via the participants attribute using the format { set: [{ email: "alice@example.com", name: "Alice" }, ...] }. People, companies, and associations are created automatically from participants. - Meetings also support summary (markdown string for meeting summary/recap) and notes (markdown string for meeting notes or transcript). Pass these as plain markdown strings in attributes. - If the meeting data includes a timezone, pass it as the timezone attribute (IANA timezone string like "America/Chicago"). Naive datetimes in start/end will be interpreted in that timezone. If no timezone is provided, the user's preferred timezone is used. UTC datetimes (with Z or offset) are stored as-is. Relationships Pass all relationships (to-one and to-many) via the relationships parameter using this format: [{ "relationshipFieldName": "<field>", "operation": "link" | "unlink", "targets": [{ "entity": "<entity>", "_id": "<id>" }] }] Use operation: "link" or operation: "unlink" when the user says link, unlink, connect, disconnect, associate, dissociate, or relate -- any create/remove of a relationship between two records, for example: - "Link this deal to Acme Corp" -> link - "Unlink this contact from the deal" -> unlink - "Connect this deal to Acme Corp" -> link - "Dissociate this contact from the deal" -> unlink A relationship-only call is valid: attributes can be omitted (it defaults to {}) when only relationships is populated, e.g. to link or unlink a record without changing any other field. The specific relationship fields available per entity are returned by get-schema with format "write". Messages are link-only Messages (email and LinkedIn) are synced from the provider — you cannot create a message or edit its fields here. You can only correct who a message is linked to. Pass the message id with a people relationship (link or unlink) and no attributes, for example to a…
create-record
ChatGPTCreate a record in Clarify.
delete-agent
ChatGPTDelete an agent by its ID. When to use this tool - When the user asks to delete or remove an agent Important notes - This action is permanent and cannot be undone Whether the user may act on something another person owns depends on their role and workspace settings, and the server makes the final decision. Do not refuse or warn about ownership up front: make the call. A manage-access read only says who can change sharing, not who can edit, disable, or delete. If the call is rejected, tell the user the error as returned. Do not retry or work around the rejection, and do not state any permission rule the tool output did not give you.
delete-artifact
ChatGPTDelete an artifact by its ID. An artifact is a named, versioned report or document that the user can view and revisit. When to use this tool Use this when the user asks to delete or remove an artifact (a report or document) they no longer want. You need the artifact's ID — if you only have its name, find it first with query-data (entity "artifact", matching name with ILIKE). How it works Deletes the artifact along with all of its saved versions. This is permanent and cannot be undone. The user is asked to confirm before the artifact is removed.
delete-calendar-event
ChatGPTCancel a calendar event. When to use this tool Use to cancel an event the user owns or has edit access to. For events the user is only invited to, use respond-to-calendar-event instead. Important notes - For recurring events, this cancels the whole series. - notify_participants defaults to true so attendees see the cancellation. - The cancellation runs as soon as this tool is called, with no confirmation step. Confirm with the user first.
delete-campaign
ChatGPTDelete a campaign by its ID. When to use this tool - When the user asks to delete or remove a campaign Important notes - This action is permanent and cannot be undone - The campaign must be disabled (draft) before it can be deleted - Only the campaign owner or an admin can delete a campaign
delete-custom-object
ChatGPTDelete a custom object type by its entity identifier. When to use this tool - When the user asks to delete or remove a custom object type Important notes - This action is permanent and cannot be undone - All records of this type will be deleted - Only custom objects (entity starting with "c_") can be deleted
delete-fields
ChatGPTDelete one or more custom fields from an existing object (built-in entities like person, company, deal, meeting, task, or custom objects like c_my_object). When to use this tool - When a user wants to remove custom fields they no longer need - When cleaning up unused or obsolete fields from any object Important - This is a destructive operation: deleted fields and their data cannot be recovered - Always confirm the deletion plan with the user before calling this tool - Built-in system fields (like a deal's amount) cannot be deleted - Relationship fields are deleted on both sides automatically
delete-list
ChatGPTDelete a list (saved view) by its ID. When to use this tool - When the user asks to delete or remove a list Important notes - This action is permanent and cannot be undone - Default lists cannot be deleted - The last remaining list for an entity cannot be deleted Whether the user may act on something another person owns depends on their role and workspace settings, and the server makes the final decision. Do not refuse or warn about ownership up front: make the call. A manage-access read only says who can change sharing, not who can edit, disable, or delete. If the call is rejected, tell the user the error as returned. Do not retry or work around the rejection, and do not state any permission rule the tool output did not give you.
delete-records
ChatGPTDelete one or more records by their IDs. Supports bulk deletion of up to 25 records per call. When to use this tool - When the user asks to delete or remove specific records Important notes - This action is permanent and cannot be undone - All relationships involving the deleted records are also removed - Use only if you already have the IDs for the records - If you need to delete more than 25 records, call this tool multiple times with batches of up to 25.
delete-workflow
ChatGPTDelete a workflow by its ID. When to use this tool - When the user asks to delete or remove a workflow Important notes - This action is permanent and cannot be undone - The workflow will stop executing after deletion - Only the workflow owner or an admin can delete a workflow
find-leads
ChatGPTSearch Clarify's built-in prospect database of companies and people to find new leads matching criteria like industry, location, headcount, job title, seniority, and funding stage, and save (publish) a search the user wants to keep. Use this for prospecting and lead discovery on any page where leads, prospects, or outbound targets are discussed. Under the hood, a search is created as a draft (or draft version) from a custom-dialect SQL query - follow the rules and examples below exactly. Modes: draft vs publish This tool has two modes, set by the mode parameter (default draft): - draft: run a SQL query to create a new draft search, or a draft refinement of an existing one, and preview the matches. This is the default and covers all prospecting and refinement. - publish: save an existing draft search so it appears under the user's "My saved searches". Call with mode: "publish", the search's search_id, and (when the search has an unapplied refinement) its version_id. No sql is needed to publish. Draft vs saved searches A draft result is not a saved search: it doesn't appear under the user's "My saved searches" until it's published. Never tell the user a search you just created is "saved"; describe it as a draft. If the user asks whether it's saved, say it's a draft and offer to save it. When the user asks to save (or "publish") a search, call this tool again with mode: "publish" and the search's search_id. Interpreting the result count The number returned is an upper bound on matching rows in the prospect database. Some of those rows may already exist in the user's CRM and some may overlap with each other. The actual number of net-new importable records can be lower and is only known at import time. Never present the match count as "unique importable leads" or use it to estimate credit spend — use it only to describe how many rows match the filters. Important Rules - Parameters like version_id and search_id should preferably come from the document context, which is the source of truth for identifiers. If the document context does not include a version_id, you may use the most recent version_id from the conversation if it is contextually relevant. When to use this tool Use this tool when a user wants to find leads matching specific criteria and preview them as a draft search: - Construct a SQL query following the rules below - Provide a search_label parameter (e.g., "SF Companies with 50+ employees") - The tool creates a draft search or draft version that the user can see and interact with (see "Draft vs saved searches" above) When not to use this tool Do not call this tool when the user's intent is ambiguous between existing CRM data and new leads. When the user searches for companies or people without using the word "lead" or "prospect", and the conversation does not already establish context, you must ask the user to clarify before calling any tool. Ask whether they want to find new leads/prospects to add to the CRM, or look up existing companies/people already in the CRM. Only call this tool after the user confirms they want new leads. Versioning vs Creating Searches When the user wants to update/modify a search: 1. Always use the SQL from the referenced version as the base query and only modify what the user explicitly asks to change. 2. Keep the same entity type and update the SQL query and search_label Update (provide search_id) when user says: "no", "actually", "refine", "filter", "change", "update", "modify" or refers to "it"/"that search"/"the search" Create (omit search_id) when user explicitly asks for a "new search" or different entity type (company vs person) SQL Rules 1. Use ILIKE, all matching is case-insensitive. 2. Database may convert ILIKE '%foo%' into 'foo%' pattern (prefix-only pattern matching) depending on field type (full text search vs keyword matching). Names are processed with prefix matching only. 3. No SELECT * — explicitly list all columns. 4. Always have the first column be the primary object field with the alias table_name:__object__.…
get-agent-runs
ChatGPTList an agent's runs, executions, invocations, and past run history, or fetch one run with its full transcript. Use this tool for any question about what an agent has done, when it ran, or how often it has fired. When to use this tool - "List runs for this agent" / "show me the agent's runs" - "Agent history" / "past runs" / "recent runs" / "last run" - "How many times has this agent run" / "count runs for an agent" - "How much did that batch cost" / "what did this agent spend today" - Debugging why an agent behaved a certain way: fetch the run to see the messages, tool calls, and responses that happened - The user provides an agent id and asks "show me what it did" Not the same as get-workflow-runs Agents and workflows both have runs but they live in different tables. Use this tool (not get-workflow-runs) whenever the subject is an agent or an agent id. Passing an agent id to get-workflow-runs silently returns nothing. How it works - Without run_id: returns a paginated list of recent runs for the agent - With run_id: returns the full run including its message transcript - A "run" is the chat session an agent produced when one of its triggers fired — user and assistant messages, tool calls, results, and errors Pagination - When the user asks for "more" runs / next page / older runs, call this tool again with the same agent_id, the same start/end if you used them, and a larger offset Reading status and credits Each run reports a Status (e.g. "In progress", "Completed", "Failed") — use it, not credits, to judge whether a run has finished. Credits Used is the cost recorded on that run so far, not a progress indicator: a response that incurs AI cost adds at least 1 credit, so 0 credits means no such response has completed. The run may be not started, still generating its first response, or have failed before producing one. Read Status for progress, and use the Created time to gauge how long a run has been running. Cost of a batch or time period To answer what runs cost ("how much did that batch cost", "what did this agent spend today"), call this tool with start and end around those runs and quote the credit total from the Window totals line. The total counts only runs created in the window, so a run started before start is left out. Every start and end needs an explicit offset (Z, +HH:MM, or -HH:MM). Set day and week boundaries in the user's timezone. If you do not know when the runs started, list runs (newest first, paging with offset until you reach the batch's first run), find its Created time, then call again with start set to that time. Never quote the agent's lifetime stats from get-agents as the cost of a batch or period, and never multiply Lifetime Avg Credits/Run by a run count for runs that already happened. The window run count may not match the agent's Lifetime Runs. Do not compare or subtract the two. Read the transcript before describing a run Whenever the user asks what a specific run did, or to summarize or explain one, call this tool with that run's run_id first. The list mode returns only counts and credits, never the messages, so describing a run from the list alone means inventing its content. Only the run_id transcript shows what happened. When the user reports that an agent isn't working as expected, proactively fetch the most recent run transcript before suggesting changes. Compare what happened in the run against the agent's instructions to diagnose the root cause. After diagnosing, explain what you found with specifics from the transcript. Suggest targeted fixes via create-or-update-agent, then offer to rerun.
get-agents
ChatGPTList agents visible to the current user, or fetch a single agent by ID. When to use this tool - When the user asks to see their agents or list available agents - When you need to look up an agent's instructions or configuration - When the user asks about a specific agent by name or ID How it works - Without agentId: returns all visible agents with description and lifetime usage stats - With agentId: returns the full agent details including complete instructions and configured triggers
get-artifact-component-doc
ChatGPTGet the full prop contract for one or more Clarify design-system components available to artifacts — each component's summary, a usage snippet, and every prop with its type and allowed values. When to use this tool The create-or-update-artifact tool lists the available @clarify/ui/* components with a one-line summary each. Call this before using components you are unsure about to get their exact props and allowed values, and call it when an existing artifact fails to render against a component so you can repair it against the current contract. How it works Pass one or more specifiers exactly as listed (e.g. @clarify/ui/button) — batch everything you need in a single call. Returns each component's documentation as markdown. Props are extracted from the component source, so the values are always current. Runtime dependency versions The artifact runtime pins framer-motion (v12.40.0), react (v19.2.8), react-dom (v19.2.8), recharts (v3.8.1). See the create-or-update-artifact tool for which of these your artifact JSX may import directly; the rest run internally (e.g. react-dom powers the scaffold's mount call). @clarify/ui/* itself is not independently versioned — it tracks this package.
get-artifact-source
ChatGPTRead the current source of an existing artifact — the JSX of its applied version, plus its name and description. When to use this tool Use this before updating an artifact so you patch its real current source rather than rewriting it from memory. It is the read half of the artifact edit loop: read the source here, apply your fix, then pass the full revised content to create-or-update-artifact with the same artifact_id to save a new version. It is especially useful when recovering from a render or query error reported to you for an artifact you did not author in this conversation. Check while you're here The saved source may carry defects. When you edit it, fold every clear fix into the same new version. The "Self-check" in the "artifact-docs" context lists the defects: dead code, broken math, manual work a built-in covers, missing contract, slow queries. Read it with read-context if you have not in this conversation. Returns The applied version's JSX source (a React component named ArtifactContents), the artifact's name, and its description. Fails if the artifact does not exist or has no applied version yet. Pagination A large source is returned one window at a time. When the window does not reach the end, the result gives the total character count and the offset to pass on the next call. Keep calling with the reported offset until you have read the region you need. This is how you reach the tail of a source too large to return in one response, so you can patch it with create-or-update-artifact.
get-calendar-events
ChatGPTList the current user's calendar events in a time range. When to use this tool - To answer "what's on my calendar today/tomorrow/next week" - To find a specific upcoming meeting by title or topic - To gather context (attendees, conferencing link) before drafting an email or creating a follow-up event How it works Reads from Clarify's synced calendar data: no live API call to Google or Microsoft. Filters meetings the current user attends in the given time range. Returns A list of events with both event_id (Nylas calendar event ID, used by the write tools) and _id (Clarify meeting ID, used by other meeting tools). When more events match than limit, the response notes the truncation. Important notes - Time range is required. Be reasonable: a 7-day window is a good default. - The optional query filter does case-insensitive substring matching on title and description.
get-campaign-recipients
ChatGPTList the individual people enrolled in a campaign, with their per-person engagement — who opened, who clicked, who replied, and when. When to use this tool - Answer "who clicked", "who opened", "who replied", "who hasn't responded" - Build a follow-up or retarget audience from the people who engaged with a campaign - Check where a specific person is in a campaign, or why their run stopped - See who a draft campaign will enroll, and who was removed from it while drafting Use get-campaigns instead when you need campaign-level totals and rates rather than the people behind them. Get the campaign_id from that tool first. 📖 For campaign rules and examples: Use read-context with context: "campaign-docs" Returns One entry per recipient: person name, email, person ID, delivery status, opened/clicked/replied flags, and the timestamp of their most recent open, click, and reply. For a campaign that has never been enabled, returns who it will enroll (list members and people added individually) and the people removed while drafting. Those people have no status or engagement yet, so event and status do not apply. Recipients change outside this chat. Call this tool again for a current answer instead of reusing an earlier result. Pagination - When the user asks for "more" results, use the EXACT same inputs and only change the offset parameter - The tool will tell you the next offset value to use Examples <example> { "campaign_id": "0195f2c1-...", "event": ["clicked"] } </example> <example> { "campaign_id": "0195f2c1-...", "status": "completed", "limit": 25 } </example>
get-campaigns
ChatGPTList campaigns in the workspace, or fetch a single campaign by ID with full details. When to use this tool - Without campaign_id: discover existing campaigns, search by name, get campaign IDs - With campaign_id: read a campaign's full email content, inspect subjects/bodies/timing, check performance metrics, get details before duplicating with create-campaign 📖 For comprehensive campaign rules and examples: Use read-context with context: "campaign-docs" Returns List mode (no campaign_id): - Campaign name, ID, description, status, email and LinkedIn step counts, target list, created/updated info, and who it sends as (the sending inbox's owner) Detail mode (with campaign_id): - Full campaign details including the LinkedIn step count and all email steps with subjects, bodies (HTML with variables), and timing - Who created the campaign and who it sends as. These can differ when the campaign sends from a delegated inbox - Campaign-level performance: open rate, click-through rate, response rate, bounce rate - Per-email-step engagement: sent, opened, clicked, replied, bounced counts Both modes, when the user has more than one inbox or a delegated one: - The inboxes they can send campaigns from, to pass as from_email to create-campaign or update-campaign All engagement here is aggregate counts only. To find out which people opened, clicked, or replied — and when — use get-campaign-recipients. Pagination (list mode only) - When user asks for "more" results, use the EXACT same search inputs and only change the offset parameter - The tool will tell you the next offset value to use Duplicating a campaign To duplicate a campaign, call this tool with campaign_id first, then pass the returned email steps and settings to create-campaign with a new name like "Copy of <original>". Copy the list_id so the duplicate targets the same audience. Do NOT copy from_name. Let the new campaign use the current user's sender identity, unless the user asks to keep the original inbox and it is one they can send from (then pass it as from_email). If the copy sends from a different inbox than the original, also remove the original sender's personal sign-off from email bodies.
get-current-user
ChatGPTGet information about the current authenticated user, including their timezone and current local time. When to use this tool Use this to understand who "me", "my", and "I" refer to in user queries. Important notes When interpreting relative dates (e.g., "last 2 weeks", "yesterday"): - Use the approximate current time from this tool's output as the reference point - Times are rounded to the nearest 10 minutes for cache coherency, so they are approximate - Calculate dates in the user's timezone - Convert to UTC and ISO format for database queries
get-lists
ChatGPTGet list metadata (saved views) for an entity type. Lists are reusable SQL queries that users have saved. Supports case-insensitive substring search - if a search query is provided, it will match against list titles and descriptions using case-insensitive substring matching. When to use this tool - Discover existing lists that might be useful for a user's query - Understand what SQL queries users have already created - Find lists by name or description Returns - List name (title) - Description - SQL query (if available) - List type (dynamic, static, default) - Layout (table, board) Pagination - When user asks for "more" results, use the EXACT same search inputs and only change the offset parameter - The tool will tell you the next offset value to use (e.g., "Call this tool again with offset=25 to continue")
get-records
ChatGPTRetrieve detailed information about specific records by their IDs. This tool provides comprehensive context about records, including relationships, AI summaries, and other details. Use this tool when you need to: - Understand the current state of specific records before answering questions or taking actions - Read meeting recording transcripts with full speaker attribution - Read email content, subject, body, and participants - Get rich context about records found via query-data Typical workflow: Use query-data to find record IDs, then pass them here for full details. Meeting-specific guidance: - For meeting: returns metadata, summary (AI-generated), notes (user-written), and associated recordings. If summary is null, check notes before concluding no content exists - For meeting_recording: returns the full transcript with speaker names and timestamps - When the user asks for exact quotes, verbatim wording, or "what exactly was said", fetch the meeting_recording transcript via get-records. The meeting summary is paraphrased and does not preserve the original words.
get-schema
ChatGPTGet the schema for Clarify entities. When to use this tool Use the read format when working with SQL queries and the query-data tool. Use the write format when creating or updating records with create-record or update-record tools. Formats - read: To get full database schema for reading data via SQL queries with the query-data tool. Returns all columns, types (including JSONB structure), relationships, join tables, and custom fields. - write: To get writable fields for create-record or update-record tools. Returns fields that can be set when creating or updating records.
get-transcript
ChatGPTRead the transcript of a meeting that is running right now, by time window. When to use this tool - The user asks what is being said, or what was just said, in a meeting in progress - The user wants a running summary, a suggested question, or a fact check while the meeting runs - You read the meeting earlier in this conversation and want only the new speech How it works - One meeting can be captured by more than one source, such as a bot and a desktop recording. This tool reads one of them, and names it as producerId. - Cursors belong to one capture. Always pass producerId and after together, taking both from the previous call. after decides where the read begins; start and end decide which turns of that read are kept. - Each call returns the finalized turns, a cursor, whether the capture is still live, and whether the result was cut short. Time window - Offsets are seconds from the start of the capture. Every turn is prefixed with its offset as [m:ss]. - start and end pick which turns are kept. 0 or more counts from the capture start. Negative counts back from the latest speech: start=-300 is the last five minutes. - One call reads one page. To continue, pass producerId and after from the trailer. start and end never move the read position on their own. - Offsets belong to one capture. When the Source line changes, restart the window instead of carrying start and end across. Returns Speaker turns in the order they were spoken, then a status line with the cursor. Important notes - Speaker labels come from the transcription provider. They are not verified identities. Cross-reference the meeting participants already in the meeting document before you attribute a turn to a person. - For a meeting that already finished and has a stored transcript, use get-records with entity="meeting_recording" instead.
get-workflow-runs
ChatGPTGet execution runs for a workflow. Returns run history with status and timing. When to use - Check if a workflow is executing correctly - Review recent run history - Count failures or investigate patterns Returns - Run ID, status, start/end times, duration - Record that triggered the run (if applicable) - To get full details of a specific run, use the get-records tool with entity "workflow-run" and the run ID Pagination - When the user asks for "more" results, use the same inputs and only change the offset
get-workflows
ChatGPTGet and search workflows in the workspace, or fetch a single workflow with its full configuration. When called without workflow_id, returns metadata for matching workflows (supports case-insensitive substring search and enabled/disabled filtering). When called with workflow_id, returns the full configuration for that one workflow (trigger with input and filters, every action block) and ignores the other inputs. When to use this tool - Find a workflow by name to get its ID - Discover existing workflows in the workspace - Get the workflow ID before calling get-workflow-runs or create-or-update-workflow - Confirm a workflow's trigger filters match what the user asked for (pass workflow_id) - Inspect a specific block's input or filters before editing it (pass workflow_id) - Walk the full block order (trigger → blocks → exits) to debug a misconfigured workflow (pass workflow_id) Returns Without workflow_id (list mode), for each workflow: - Workflow ID - Name and description - Status (active/draft) - Trigger plugin ID - Number of action blocks - Created and updated timestamps With workflow_id (single mode): - Workflow ID, name, description, status (active/draft), and timestamps - Trigger: plugin ID, input, filters - Each block: ID, plugin ID, input, filters, prev/next links Pagination When the user asks for "more" results, use the same inputs and only change the offset
import-leads
ChatGPTImport leads from a tam_company or tam_person search into Clarify. Important Rules - Parameters like versionId and searchId should preferably come from the document context, which is the source of truth for identifiers. If the document context does not include a versionId, you may use the most recent versionId from the conversation if it is contextually relevant. - When a versionId is provided, the version will be applied to the search and the search will be published before starting the import - Always provide a descriptive searchTitle that reflects the current filters/query of the search (e.g., "SF Companies with 50+ employees") - Always provide a searchEmoji — a single emoji that represents the theme of the search (e.g., "🏢" for companies, "🌉" for SF-related searches) When the user asks to import leads without specifying a count: - For tam_company searches, import all leads without asking — company imports never consume credits regardless of size. - For tam_person searches with fewer than 100 leads, import them all without asking. - For tam_person searches with 100 or more leads, ask whether they want to import a small sample (e.g. 10) to test first or all leads from the search. Inform the user that importing people costs 1 credit(s) per net-new person, so importing all N people will cost up to N × 1 credits total. How charges work Credits are charged only for net-new tam_person records that get inserted. Records that already exist in the CRM are skipped by the system and never charged. tam_company imports are always free. When a batch returns 0 net-new records (all matches already exist in the CRM), nothing is charged. Never tell the user they have "burned credits on duplicates" or suggest a refund in that case; there is nothing to refund. Never explain internal decision logic, thresholds, or tool behavior to the user. For example, do not say things like "Since there are fewer than 100, I'll import them all." Just perform the action and describe what you did in user-friendly terms. Extra fields This tool can import any field from the TAM database — not just the core fields. When the user asks for additional data (e.g., "import with industries and tech stack", "I want ownership status too", "add seniority"), use the extraFields parameter. The import automatically creates CRM fields that don't exist yet and populates them with TAM data. Do not create fields manually — this tool handles it. This also works for records that already exist in the CRM. If the user wants to backfill a field (e.g., "add industries to my existing companies"), re-run the import with extraFields and the existing records will be updated with the new field values. Available extra fields for companies: alternative_names (Alternative names), website (Website), type (Type), industries (Industries), specialities (Specialities), workforce_headcount (Workforce headcount), summary (Summary), funding_last_round_type (Last funding round type), funding_last_round_amount_usd (Last funding round amount (USD)), funding_last_round_date (Last funding round date), investments (Investments), financing_profile_status (Financing status), financing_profile_ipo_date (IPO date), financing_profile_market_cap (Market cap), ownership_status (Ownership status), ownership_status_detailed (Ownership status (detailed)), stock_exchange (Stock exchange), customer_types (Customer types), tech_stack_products (Tech stack products), patent_count (Patent count), contact_info_email (Contact email), contact_info_phone (Contact phone), contact_info_url (Contact URL), identifiers_stock_ticker (Stock ticker), identifiers_naics_code (NAICS code), identifiers_duns_code (DUNS code), identifiers_cage_code (CAGE code), is_acquired (Is acquired), is_exited (Is exited), is_government (Is government), is_non_profit (Is non-profit), is_shut_down (Is shut down), is_stealth (Is stealth). Available extra fields for people: personal_email_addresses (Personal email addresses), headline (Headline), function (Function), seniority (Seniority), hiring (Hiring), open_to_work (Open to work), skil…
import-meeting-transcript
ChatGPTImport a meeting transcript from an external source (Granola, Notion, Circleback). When to use this tool Use after finding a meeting in an external source. Pass the source and transcript ID directly; the tool fetches the content from the source system. How it works 1. Provide the Clarify meeting ID to attach the transcript to 2. Specify the source (granola, notion, or circleback) 3. Provide the transcript/page ID from the source system The tool calls the source's MCP server directly to fetch the transcript, parses it, and uploads it to the meeting. Important notes - The workspace must have the source MCP server connected - The user must have authorized with the source MCP server - For Circleback: the source_id must be the numeric meeting ID (e.g. "7927294"), not the URL slug. Use the Circleback SearchMeetings tool first to resolve the numeric ID. - After importing, tell the user the transcript was attached
manage-access
ChatGPTManage who can access a specific object — list, meeting, message, task, agent, ai_chat, ai_skill, company, person, deal, artifact, or a custom object — by granting, updating, revoking, or reading its access grants, or reassigning its owner. Also requests access to a meeting or email the user cannot open. When to use this tool - When the user asks to share a list, meeting, message, task, or record with a teammate or with the whole workspace - When the user asks to share every email/message on a record (e.g. "share all emails on this deal with the team") — use mode: "related" - When the user asks to change someone's access level (view vs edit) - When the user asks to stop sharing / remove access - When the user asks who an object is shared with, who can see it, who has access to it, or what access someone has — use action: "read" - When the user asks to change who owns a list or record — reassign ownership to someone else - When the user wants a meeting or email they cannot open, or asks to request access to one: use action: "request" How it works Set action to one of "grant", "update", "revoke", "read", "reassign", or "request" and point target at what you're managing. target has two modes: - mode: "direct" — a single object by entity (list, meeting, message, task, agent, ai_chat, ai_skill, company, person, deal, artifact, or a custom object) and objectId. Supports every action (request: meetings and messages only). - mode: "related" — every message related to an anchor record, addressed by anchorEntity (e.g. deal, company, person), anchorId, and relatedEntity: "message". Bulk-shares the messages you own on that record. Supports grant, revoke, and read only (no update or reassign). - grant / update: pass grantees and an accessLevel ("view" or "edit"). - revoke: pass either grantees or all: true (not both). Revoke asks the user to confirm before removing access. - read: returns the current grantees and their access levels, and whether you can change access on the object (and why not, if you can't). - reassign: pass newOwnerId (a user record id) to transfer ownership to that user. The previous owner keeps edit access. - request: asks the people who can share a meeting or email (entity "meeting" or "message") to give the user access, and notifies them. Pass message when the user says why they need it. Calling it again reports whether the request is still pending instead of filing a duplicate. They approve or decline in Clarify, not through this tool. To act on several objects of the same entity in one call — share, update, revoke, or reassign — list the other object ids in objectIds (target.objectId is always included). They are applied best-effort: objects you can't manage are skipped and reported, the rest still go through. Grantees A grantee is { granteeType, userId }: - granteeType: "user" shares with one person and requires userId (a user record id — resolve names/emails to an id first). - granteeType: "workspace" shares with everyone in the workspace and takes no id. Important notes - entity is the object's actual type, not what it resembles: an artifact (dashboard, report) is entity: "artifact", never a list. When the object comes from the page context, use that resource's type as the entity. - Only the owner (or a workspace permission admin) can change an object's access or reassign its ownership; meetings require a participant. Unauthorized requests are rejected. - A workspace admin can reassign the owner of a record only when it is workspace-visible, never a private one. The owner can always reassign their own, and list ownership transfer has no such restriction. - Meetings, messages, and tasks have no owner and cannot be reassigned. - A meeting's participants and a message's recipients are not grantees; only read returns the actual share list. - In related mode, only the messages you own on the anchor record are shared; messages you don't own are never touched. - Access levels are "view" or "edit" only.
merge-records
ChatGPTMerge two or more records into a single primary record in Clarify. All data from the source records (fields, relationships, list memberships, notes) will be merged into the primary record. Source records are deleted after merging. Merge up to 50 groups of duplicates in one call. Before calling, confirm with the user which record in each group is the primary.
prepare-attachment-upload
ChatGPTStart attaching a file to a CRM record by requesting a one-time upload URL. When to use this tool Use this as the first step when you have file bytes to attach to a record and the file was not uploaded in a chat. It returns a presigned URL you upload the bytes to; then call create-attachment-from-upload to attach the uploaded file to the record. How it works Pass the record entity and the file name (including its extension). The tool returns an upload_url, a key, and an attachment_id. Send the raw file bytes with an HTTP PUT to upload_url within 5 minutes, then call create-attachment-from-upload with the record id, the key, the attachment_id, and the same name. Files must be one of the supported types and no larger than 250MB.
query-analytics
ChatGPTExecute read-only ClickHouse SQL against the analytics event log. When to use this tool Use query-analytics for historical / point-in-time questions about CRM records: anything that requires looking at the sequence of changes over time, not just the current state. Examples: - "What was the sequence of stage changes for this deal?" - "How long did this deal sit in 'In progress'?" - "What was the total amount of all deals in each stage on a specific date?" - "How many deals were created per day last week?" - "Who last edited this record, and when?" When NOT to use this tool For current state questions ("which deals are open?", "what's this company's domain?"), use the query-data tool: it queries PostgreSQL directly and is faster for live values. Only fall back to query-analytics when you need history. How it works - The query runs against ClickHouse, not PostgreSQL. Use ClickHouse SQL syntax (JSONExtractString, toDate, argMax, etc.). - When the user hasn't asked for a specific time frame, scope the query to the last 30 days with AND timestamp >= now() - INTERVAL 30 DAY to keep it fast and the results relevant. - Statements are read-only: INSERT / UPDATE / DELETE / DDL are rejected by ClickHouse. - Results are capped to the maxResultRows parameter (default 1024). Rows beyond the cap are dropped server-side, even if your SQL omits a LIMIT. Analytics database (ClickHouse) Historical event log of every change to the CRM. Read-only. Query it to answer point-in-time and change-history questions like: - "What was the sequence of events that led to the current stage of this deal?" - "How long did this deal sit in the 'In progress' stage?" - "How many deals were created per day last week?" The analytics.event table (ClickHouse) Read-only SQL runs against ClickHouse, not PostgreSQL — use ClickHouse syntax (JSONExtractString, toDate, argMax, etc.). Every CRM record lives in ONE append-only CDC event log, analytics.event — NOT a table of current records; every create/update/delete is its own row. There is no deal / company / person table (FROM deal fails with "Unknown table expression identifier"). Always query FROM analytics.event and pick the entity with WHERE entity_type = '<type>'. Hot keys are promoted to materialized columns (below); every other field is read from properties with JSONExtract*. Every event carries a FULL snapshot of the record's fields in properties — create, update, and delete alike. An update is not a delta: it repeats every field the record has, not just the ones that changed. So the single latest event per entity already holds every current field, and you never need to stitch fields together across events. (The one exception: a field you can't access is absent from properties.) - entity_type (String) — Subject type: deal, company, person, meeting, message, or c_<slug> for a custom object - entity_id (String) — Subject record id - type (String): clarify:create, clarify:update, clarify:delete, or clarify:merge (an update to the record that survived a merge; the merged-away records get a clarify:delete) - timestamp (DateTime64(6,'UTC')) — Event time; use toDate(timestamp) for day grouping - properties (String/JSON) — A full snapshot of the record's fields at event time (every field, not just the changed ones); read with JSONExtractString/Float/Bool(properties, '<field>') - diff (String/JSON) — On update events, the field-level changes this event applied: a JSON array of {op, path, val, oldVal} (path locates the field, oldVal → val is the transition). Empty ('') on create and no-op events. Use it to detect WHEN a field changed — see "Detecting when a field changed" - actor (Tuple): actor._id, actor.entity, actor.source_id (all String, native paths, no JSONExtract needed); usable in GROUP BY / ORDER BY - Use actor.entity and actor.source_id to tell an automated actor (an agent or an API key) from a human user, and group or exclude by them when an automation may skew a metric -…
query-data
ChatGPTExecute PostgreSQL queries to retrieve data from Clarify. When to use this tool Analytics mode - Default - Counting records (e.g., "How many deals are open?") - Aggregations (SUM, AVG, COUNT, MAX, MIN, GROUP BY) - List queries (e.g., "Show me 50 recent meetings") - Historical analysis (stage transitions, field changes over time) - Searching/filtering large datasets (e.g., "Show all deals from Q4" or "Find records updated this week") Rich context mode - Understanding context about a FEW specific records - Answering questions that need full record details and relationships - Reading meeting recording transcripts with full speaker attribution - Example: "Tell me about deal XYZ" or "Show me details of the top 5 deals" Working with JSONB Columns Many columns store data as JSONB objects. Check the "Available Tables and Columns" section to identify JSONB columns. JSONB Access Operators - -> - Extract JSON object (returns JSONB): column->'key' - ->> - Extract text value (returns TEXT): column->>'key' - jsonb_array_elements(column->'items') - Iterate over JSONB arrays JSONB Column Patterns Object with nested fields (e.g., JSONB with format {first_name: string, last_name: string}): - Access nested field: column->>'first_name' - Concatenate fields: CONCAT(column->>'first_name', ' ', column->>'last_name') - Filter: column->>'first_name' ILIKE '%john%' Array of items (e.g., JSONB with format {items: string[]}): - Get first item: column->'items'->>0 - Iterate items: jsonb_array_elements(column->'items') as item - Access nested in item: (item->>'email') Important notes - Only use JSONB operators (->, ->>) on columns explicitly marked as "JSONB with format" in the schema. Regular string columns use standard SQL operators (ILIKE, =, etc.) - Always prefix columns with table names (e.g., person.name, not name) - Use ILIKE for case-insensitive matching (not LIKE) - Query timestamps using UTC/ISO 8601 format (e.g., '2025-12-22T00:00:00Z') - Results will have timestamps formatted in the user's timezone for readability - For many-to-many relationships, use INNER JOIN with join tables Time-Based Queries Use the current time from get-current-user tool or system context as reference. "Last" vs "Next" Terminology - "last meeting" = Most recent past meeting: WHERE meeting.start < NOW() ORDER BY meeting.start DESC LIMIT 1 - "next meeting" = Soonest future meeting: WHERE meeting.start > NOW() ORDER BY meeting.start ASC LIMIT 1 - "recent meetings" = Meetings in the past, not upcoming ones - "upcoming meetings" = Meetings in the future Time Period Interpretation When user mentions "Q4 2025", "last quarter", or similar: - Activity in that period: Filter by date: WHERE meeting.start >= '2025-10-01' AND meeting.start < '2026-01-01' - Content mentioning that period (e.g., "meetings where Q4 was discussed"): Search transcripts/summaries, not date filter Before Writing SQL - Review the "Available Tables and Columns" section for exact column names and types - Call get-current-user if the query involves "me", "my", or "I", if the tool is available User-Scoped Queries When querying data related to "me", "my", or "I": - Use the current user's ID to filter records - Remember: user and person are different tables - user = internal workspace members, person = external contacts - For meetings: INNER JOIN user_meeting ON meeting._id = user_meeting.meeting_id WHERE user_meeting.user_id = '<user-id>' - External meetings have attendees who are not workspace users. Check person_meeting but exclude persons whose email matches a user: EXISTS (SELECT 1 FROM person_meeting pm JOIN person p ON p._id = pm.person_id WHERE pm.meeting_id = meeting._id AND NOT EXISTS (SELECT 1 FROM "user" u WHERE u.email IN (SELECT jsonb_array_elements_text(p.email_addresses->'items')))) - Internal meetings have no truly external attendees (inverse of above) - For tasks: Filter by assignee_id: WHERE task.assignee_id = '<user-id>' - For deals: Filter by owner_id: WHERE deal.owner_id = '<user-id>' Activity Counting Activity includes: meetings, messages, tasks, and comments. To count activity: - Comments: Query comment table with entity = '<entity-type>' and owner_id = <entity>._id. Use _created_at as timestamp. - Meetings: Join through person_meeting join table. If entity relates to person via join table (e.g., person_deal) or foreign key (e.g., person.company_id), join: entity → person → person_meeting → meeting. Use meeting.start as timestamp. - Messages: Join through person_message join table. Same pattern as meetings: entity → person → person_message → message. Use message.received_at as timestamp. - Tasks: Join via foreign key task.deal_id. For deals: direct join. For companies: join deal first (via deal.company_id), then task. Use task._created_at as timestamp. Pattern: Create CTEs for each activity type, UNION ALL them, then LEFT JOIN to your entity table. Use COUNT() and MAX() for totals and last activity date. Pagination - When user asks for "more" results, use the EXACT same SQL query and only change the offset parameter - The tool will tell you the next offset value to use (e.g., "Call this tool again with offset=25 to continue")
read-context
ChatGPTRead Clarify product knowledge and documentation. Use this whenever you need to understand a feature, apply best practices, or reference detailed technical documentation. Available contexts: - field-docs: Field type guidelines, AI prompt writing instructions, available relationship data, and examples for creating or updating custom fields - workflow-docs: Documentation about how the Workflows feature works. Workflows are event-based programmatic automation of the CRM. - calendar-docs: Guide for the calendar tools: timezone handling, timed vs all-day events, resolving participants by name via CRM lookup, participant rules (always include the user), choosing which connected account a create lands on (other writes follow the event owner), update-replaces-not-merges semantics, recurrence (RRULE), notify_participants defaults, and event-ID conventions. - campaign-docs: Complete guide to email campaigns including structure, variables, enrollment, exit conditions, and best practices - artifact-docs: The required data rules for authoring an artifact: which store its SQL runs on, how to prove a query, and how to size a result for the frame, plus what makes a dashboard, QBR, or sales document effective. Read before you write SQL for a dashboard, report, or artifact. - list-docs: How to write a list's defining SQL: title alias, showing to-one relationships, and column restrictions
respond-to-calendar-event
ChatGPTRSVP to a calendar event you've been invited to. When to use this tool Use to accept, decline, or tentatively accept a meeting invite. The user must be a participant on the event. Important notes - The RSVP sends as soon as this tool is called, with no confirmation step. Confirm with the user first. - Status values: yes (accept), no (decline), maybe (tentative).
send-email
ChatGPTsubmit-feedback
ChatGPTSubmit concise feature requests or feedback about MCP tools. Use when: - Missing functionality prevents completing a user's request - User requests a new feature or reports a limitation - User suggests improvements Keep feedback brief and focused on what's needed or what could be improved.
update-campaign
ChatGPTUpdate an existing email campaign: rename it, change its target list, adjust the sender, edit its email steps, or configure send time windows. To create a new campaign, use the create-campaign tool instead. Requires a campaign_id (get it from the get-campaigns tool). 📖 For comprehensive campaign rules and examples: Use read-context with context: "campaign-docs" When to use this tool - When the user asks to rename a campaign, change its target list, adjust the sender, or edit email steps (subjects, bodies, timing) - When the user asks to configure when a campaign's emails can be sent (send time windows) Editing email steps Each email_step must specify an operation: - update: Modify an existing step (requires step_number, optional: subject, body) - remove: Delete a step (requires step_number) - insert: Add a new step (requires step_number, subject, body, delay_after_days) To append at the end, use step_number equal to the current number of steps + 1. A update with body replaces the entire body of that step. To add to or tweak part of an existing email, first read the current body with get-campaigns, apply your change to it, and send the full updated body. Never regenerate a body from scratch for a targeted edit, or you will discard the user's existing content. If you cannot make a requested change without replacing content the user wrote, confirm with them first. Campaign email bodies support text and images. Tables, videos, and other embedded content are not supported and are silently dropped. Do not attempt to insert those; tell the user they aren't supported yet. An image must be an <img src="..."> pointing at an already-hosted public URL. A plain link to a file (for example a PDF URL) is fine. Essential rules 1. Email/Delay Pattern: Emails and delays MUST alternate. When inserting an email, always specify delay_after_days 2. First email: delay_after_days: 0. Subsequent emails: minimum 1 day 3. Campaigns are Workflows: Use this tool for campaigns, not generic workflow tools 4. Campaign emails are templates sent to multiple people 5. Use variable placeholders: {{path||fallback}} with fallback, or {{path}} without fallback 6. Only use the variables listed below (do not invent variables) 7. A live campaign (enabled or already sent) accepts only new_name and send_windows. Its steps, list, and sender are locked Threading: send_as_reply Set send_as_reply: true on a step to thread it as a reply under the previous step (uses the prior message id and "Re: <previous subject>"). Use it when a follow-up bumps the prior email and the recipient should see it inline: - Use send_as_reply: true for content like "circling back", "just bumping this", "wanted to follow up", or any step that explicitly references the prior email's call to action without a new pitch. - Leave it off (default) when the step introduces a new angle, case study, or call to action; recipients triage by subject, and a fresh subject signals fresh content. Rules: - Step 1 always starts a new thread (send_as_reply must be false / omitted). - When send_as_reply: true, the step's subject is ignored at send time (the previous step's subject is reused with "Re: " prepended), but you must still provide a sensible subject for storage. Fallback rules (fallbacks make emails feel natural when data is missing): - ALWAYS include fallbacks for human-identifiable information: → Person names (first_name, last_name, full_name) → use "there", "Friend", etc. → Company/organization names → use "your company", "your organization", "your team" → Job titles and roles → use "your role", etc. → Location/city names → use "your area", "your region", etc. - Skip fallbacks ONLY for technical/structured data: → URLs, email addresses, phone numbers → Dates, timestamps, IDs → Numerical values and metrics Examples <example> Update a campaign step: { "campaign_id": "abc-123-def", "email_steps": [ { "operation": "update", "step_number": 2, "subject": "Quick follow-up, {{person.name.first_name||there}}" } ] } </e…
update-record
ChatGPTUpdate a record by its ID in Clarify. This should be used only if you already have the ID for the record.
create-campaign
Claudecreate-custom-object
Claudecreate-list
Claudecreate-or-update-agent
Claudecreate-or-update-fields
Claudecreate-records
Claudedelete-agent
Claudedelete-fields
Claudefind-leads
Claudeget-agent-runs
Claudeget-agents
Claudeget-campaign
Claudeget-current-user
Claudeget-lists
Claudeget-records
Claudeget-schema
Claudeimport-leads
Claudelist-campaigns
Claudemerge-records
Claudequery-data
Claudesubmit-feedback
Claudeupdate-campaign
Claudeupdate-custom-object
Claudeupdate-list
Claudeupdate-records
Claude