diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index c9a4b553dc1..7b121cbc3a0 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2221,21 +2221,108 @@ export function EyeIcon(props: SVGProps) { ) } +/** + * Corporate Atlassian mark, used for family-wide Atlassian credentials — one + * API token authenticates Jira, Jira Service Management, and Confluence, so no + * single product mark represents it. Individual products keep their own icons. + */ +export function AtlassianIcon(props: SVGProps) { + const id = useId() + const gradientId = `atlassian_gradient_${id}` + + return ( + + ) +} + export function ConfluenceIcon(props: SVGProps) { + const id = useId() + const topGradientId = `confluence_top_${id}` + const bottomGradientId = `confluence_bottom_${id}` + return ( ) @@ -2768,19 +2855,58 @@ export function LinkupIcon(props: SVGProps) { } export function JiraIcon(props: SVGProps) { + const id = useId() + const middleGradientId = `jira_middle_${id}` + const bottomGradientId = `jira_bottom_${id}` + return ( ) @@ -7558,6 +7684,31 @@ export function SixtyfourIcon(props: SVGProps) { ) } +/** + * The "sim" brand wordmark (v1.0 brand guide simLogotype paths — the same mark + * the navbar/login header renders), inked with the theme-adaptive + * `--text-body`. Used as the icon for the Auto model option; the wide viewBox + * letterboxes itself inside square icon slots. + */ +export function SimAutoIcon(props: SVGProps) { + return ( + + ) +} + export function SimTriggerIcon(props: SVGProps) { return ( ) { ) } + +export function ZohoDeskIcon(props: SVGProps) { + return ( + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 8070694868b..283ff057a5f 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -248,6 +248,7 @@ import { ZendeskIcon, ZepIcon, ZeroBounceIcon, + ZohoDeskIcon, ZoomIcon, ZoomInfoIcon, } from '@/components/icons' @@ -535,6 +536,7 @@ export const blockTypeToIconMap: Record = { zendesk: ZendeskIcon, zep: ZepIcon, zerobounce: ZeroBounceIcon, + zoho_desk: ZohoDeskIcon, zoom: ZoomIcon, zoominfo: ZoomInfoIcon, } diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index 56235d7d9c2..ef27475a3a9 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -261,6 +261,8 @@ "zendesk", "zep", "zerobounce", + "zoho-desk-service-account", + "zoho_desk", "zoom", "zoom-service-account", "zoominfo" diff --git a/apps/docs/content/docs/en/integrations/zoho-desk-service-account.mdx b/apps/docs/content/docs/en/integrations/zoho-desk-service-account.mdx new file mode 100644 index 00000000000..d1cee116931 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/zoho-desk-service-account.mdx @@ -0,0 +1,162 @@ +--- +title: Zoho Desk Self Clients +description: Set up a Zoho Self Client so your workflows can call Zoho Desk without a personal OAuth login +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Step, Steps } from 'fumadocs-ui/components/steps' +import { FAQ } from '@/components/ui/faq' + +A Zoho **Self Client** is an OAuth client that has no redirect URL and no end user. Instead of sending someone through a consent screen, your workflows authenticate to Zoho Desk with the client's own ID and secret, and Sim mints a short-lived access token on demand — no user consent to expire, and no personal login that breaks when someone leaves the team. + +This is the recommended way to use Zoho Desk in production workflows: the credential belongs to your organization rather than a person, the granted scopes are explicit, and tokens are minted fresh whenever a workflow runs. + +## Prerequisites + +You need a Zoho account with access to the [Zoho API Console](https://api-console.zoho.com) for the same organization your Zoho Desk portal belongs to, and the Zoho Desk **organization ID** for that portal. + + +A Self Client can authenticate against the **US, EU, IN, or AU** accounts server — pick your region with the **Data center** field when you add the credential, or leave it blank for US. Organizations in the JP, CA, SA, CN, or UK data centers are not supported yet. API calls are then routed to the Desk host for that same region, so data residency is honored end to end. + +The interactive **OAuth** connection is a separate path and remains **US-only** (`accounts.zoho.com`), so a non-US organization can connect only through a Self Client. + + + +Zoho Desk **webhooks are a Professional-edition and above feature**. The Zoho Desk trigger in Sim provisions a webhook subscription, so it does not work on Free or Standard plans. The trigger also requires a personal **OAuth** connection rather than a Self Client — see [Triggers](#triggers-still-need-oauth) below. + + +## Setting Up the Self Client + +### 1. Create the Self Client + + + + Sign in at [api-console.zoho.com](https://api-console.zoho.com) with the Zoho account that owns the Desk portal + + + Click **Add Client**, choose **Self Client**, and click **Create** — then **OK** on the confirmation + + {/* TODO(screenshot): Zoho API Console Add Client dialog with Self Client selected */} + + + Open the new client and switch to the **Client Secret** tab. Copy the **Client ID** and **Client Secret** — these are two of the three values you'll paste into Sim + + {/* TODO(screenshot): Self Client Client Secret tab showing Client ID and Client Secret */} + + + + +You do **not** need the **Generate Code** tab. That tab produces a one-time authorization code for the code-exchange flow; the client-credentials flow Sim uses needs only the client ID, the client secret, and your organization ID. + + +### 2. Find Your Organization ID + + + + Open Zoho Desk and go to **Setup** (the gear icon) → **Developer Space** → **API** + + + Copy the numeric **Organization ID** (also called the Org ID or portal ID) shown there — for example `600123456` + + {/* TODO(screenshot): Zoho Desk Setup > Developer Space > API showing the Organization ID */} + + + + +This must be the organization ID of the Desk portal you want the workflows to act on. If your Zoho account has more than one Desk portal, using the wrong ID makes Zoho either reject the token request or issue a token scoped to the wrong portal. + + +### 3. Know Your Data Center + +Zoho hosts each organization in one data center, and the accounts server that issues tokens is per region. Look at the URL you use to sign in to Zoho Desk and pick the matching code: + +| Data center | Sign-in domain | Code to enter | +| --- | --- | --- | +| United States | `zoho.com` | `us` (or leave blank) | +| Europe | `zoho.eu` | `eu` | +| India | `zoho.in` | `in` | +| Australia | `zoho.com.au` | `au` | + +Organizations in the JP, CA, SA, CN, and UK data centers cannot be connected yet. + +### 4. Scopes + +Sim requests exactly the scopes its Zoho Desk tools and trigger exercise: + +``` +Desk.tickets.READ +Desk.tickets.UPDATE +Desk.contacts.READ +Desk.agents.READ +Desk.basic.READ +Desk.webhooks.CREATE +Desk.webhooks.DELETE +aaaserver.profile.READ +``` + +Sim sends this list on every token request, so there is nothing to pre-configure on the Self Client itself. If Zoho rejects the request with an invalid-scope error, the Self Client's owner does not have access to one of the Desk modules above in that organization. + +A scope that is granted but insufficient surfaces at run time as a `4xx` from the Zoho Desk API naming the scope problem. + +### 5. Protect the Client Secret + +The client secret is bearer material for your Zoho Desk organization, limited only by the scopes above. Treat it like a password — do not commit it to source control or share it publicly. Sim encrypts it at rest. + + +Regenerating or revoking the Self Client in the Zoho API Console invalidates the stored pair immediately. If you rotate it, update the credential in Sim right away. + + +## Adding the Self Client to Sim + + + + Open **Integrations** from your workspace sidebar + + + Search for "Zoho Desk" and open it, then click **Add to Sim** and choose **Add Self Client** + + {/* TODO(screenshot): Zoho Desk integration page with the Add Self Client connect option */} + + + In the **Add Zoho Desk Self Client** dialog, paste the **Client ID**, the **Client secret**, and the numeric **Organization ID**. Set **Data center** to your region (`us`, `eu`, `in`, or `au`) — leave it blank for US. Optionally set a display name and description + + {/* TODO(screenshot): Add Zoho Desk Self Client dialog with all fields filled in */} + + + Click **Add Self Client**. Sim verifies the credentials by minting a real access token from Zoho — if it fails, the error tells you whether Zoho rejected the credentials or couldn't be reached. + + + +## Using the Self Client in Workflows + +Add a Zoho Desk block to your workflow. In the credential dropdown, your Self Client appears alongside any OAuth credentials. Select it and configure the block as you normally would. + +{/* TODO(screenshot): Zoho Desk block in a workflow with the Self Client selected as the credential */} + +The block calls the Zoho Desk REST API with a freshly minted access token — the same requests as the OAuth flow, so the Zoho Desk tools work the same way, subject to the scopes above. + +One exception is worth knowing: Zoho publishes no OAuth scope for the attachment download sub-path, so **Get Attachment** is the one operation whose scope requirement we could not confirm from Zoho's documentation. If it returns a scope error, the requested scope list needs widening. + +### Triggers still need OAuth + +The Zoho Desk **trigger** provisions and tears down its own webhook subscription against your Desk organization, and that provisioning path currently runs only against a personal OAuth connection. Connect a Zoho Desk account through OAuth for triggers, and use the Self Client for the blocks that read and update tickets. + +Because the OAuth flow is US-only, an organization outside the US data center can use Zoho Desk blocks through a Self Client but cannot use the Zoho Desk trigger. + +## Token Behavior + +Access tokens minted from a Self Client live for one hour and there is **no refresh token** — Sim mints a new token whenever one is needed, and caches the current one until it is close to expiry. Two events invalidate the stored credential: + +- **Revoking or deleting the Self Client** in the Zoho API Console — no new tokens can be minted +- **Regenerating the client secret** — the stored pair stops working; paste the new secret into the credential in Sim + + value instead." }, + { question: "Zoho rejects my credentials with invalid_client — why?", answer: "Either the client ID or secret was mistyped, or the client you created is not a Self Client. Only Self Clients support the client-credentials grant — in the Zoho API Console, Add Client → Self Client. Copy both values from the client's Client Secret tab." }, + { question: "Zoho returns missing_org_info or rejects the organization — why?", answer: "Zoho could not resolve a Desk organization from the ID you pasted. Re-copy the numeric Organization ID from Setup → Developer Space → API in the Desk portal you want to use. If your Zoho account has multiple Desk portals, make sure it is the ID of the right one." }, + { question: "Can I use a Self Client with a non-US Zoho account?", answer: "Yes, for the US, EU, IN, and AU data centers. Set the Data center field to us, eu, in, or au when you add the credential, and Sim mints tokens against that region's accounts server and calls the Desk host in the same region. Leaving it blank means US. The JP, CA, SA, CN, and UK data centers are not supported yet, and the interactive OAuth connection remains US-only." }, + { question: "I picked the wrong data center — what happens?", answer: "The region's accounts server does not know your organization, so Zoho rejects the token request and Sim reports that it could not authenticate. Edit the credential and set the Data center to the region whose domain you sign in to Zoho Desk with." }, + { question: "Why doesn't my Zoho Desk trigger work with the Self Client?", answer: "The trigger provisions a webhook subscription in your Desk organization, and that path runs against a personal OAuth connection only. Connect Zoho Desk through OAuth for triggers. Separately, Zoho Desk webhooks require a Professional-edition plan or above — they are unavailable on Free and Standard." }, + { question: "How do I rotate the credentials?", answer: "Regenerate the client secret on the Self Client's Client Secret tab in the Zoho API Console, then update the credential in Sim with the new secret. The old secret stops working as soon as it's regenerated, so update Sim promptly." }, +]} /> diff --git a/apps/docs/content/docs/en/integrations/zoho_desk.mdx b/apps/docs/content/docs/en/integrations/zoho_desk.mdx new file mode 100644 index 00000000000..727f0fd9a02 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/zoho_desk.mdx @@ -0,0 +1,515 @@ +--- +title: Zoho Desk +description: Manage Zoho Desk tickets, comments, threads, and contacts +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Zoho Desk](https://www.zoho.com/desk/) is Zoho's customer support help desk. Support teams use it to receive tickets from email, web forms, chat, phone, and social channels, route them to the right department and agent, and track every customer conversation through to resolution. + +With the Sim Zoho Desk integration, you can: + +- **Read and filter tickets**: List tickets across an organization filtered by department, status, or priority, or fetch a single ticket by ID with its related contact, assignee, and department. +- **Update tickets**: Change subject, status, priority, assignee, department, category, due date, and custom fields — useful for AI triage that classifies an incoming ticket and writes the result back. +- **Work with conversations**: List and read ticket threads (the customer-facing email/chat exchange) and comments (internal agent notes), then add your own comment as public or private. +- **Look up contacts**: Retrieve the contact behind a ticket to enrich it with data from your CRM or knowledge base. +- **Download attachments**: Pull an attachment from a thread or comment into a Sim file you can pass to downstream blocks. +- **Trigger on events**: Start a workflow when a ticket, comment, thread, contact, agent, task, or article changes in Zoho Desk. + +**How it works in Sim:** +Add a Zoho Desk block to your workflow, connect your Zoho account, and pick the Organization (portal) to work in — Sim loads the list for you from the connected account. Choose an operation and fill in its parameters; the block calls the Zoho Desk API and returns structured data for downstream blocks. For comment and thread bodies, Sim adds a derived plain-text `contentText` field alongside Zoho's raw HTML `content`, so an AI agent can read the message without HTML markup. + +To trigger on Zoho Desk activity instead, use the block's trigger mode. Sim creates the webhook subscription in Zoho Desk for you and removes it automatically when the workflow is undeployed. + +**Requirements and limitations** + +> Zoho Desk webhooks require a Zoho Desk edition of **Professional or higher** — Free and Standard plans cannot create webhook subscriptions, so the trigger will fail to deploy on those plans. +> +> Connecting a Zoho account with **OAuth** — which the trigger requires — works only for the **US data center** (`accounts.zoho.com`). To use Zoho Desk blocks from the EU, India, or Australia data centers, connect a [Self Client](/integrations/zoho-desk-service-account) instead and set its data center. The Japan, Canada, Saudi Arabia, China, and UK data centers are not supported by either path. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Read and update Zoho Desk tickets, manage comments and threads, look up contacts, and download attachments. Can also trigger workflows from Zoho Desk webhook events. + + + +## Actions + +### `zoho_desk_list_tickets` + +List tickets from a Zoho Desk organization with optional filters. Returns a list projection: description, resolution, statusType and classification are only available from Get Ticket. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `from` | number | No | Pagination start index \(0-based, max 4999\) | +| `limit` | number | No | Number of tickets to return \(1-100, default 10\) | +| `departmentIds` | string | No | Filter by department ID \(comma-separated for multiple\) | +| `status` | string | No | Filter by status, including custom statuses. Comma-separate to match multiple \(e.g. "Open,On Hold"\) | +| `priority` | string | No | Filter by priority. Comma-separate to match multiple \(e.g. "High,Urgent"\) | +| `sortBy` | string | No | Sort field: createdTime, customerResponseTime, or responseDueDate. Prefix with - for descending. | +| `include` | string | No | Comma-separated related data to embed. Allowed: contacts, products, departments, team, isRead, assignee | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tickets` | array | List of tickets | +| ↳ `id` | string | Ticket ID | +| ↳ `ticketNumber` | string | Human-readable ticket number | +| ↳ `subject` | string | Ticket subject | +| ↳ `description` | string | Ticket description \(raw; may be HTML\) | +| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim | +| ↳ `status` | string | Ticket status | +| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) | +| ↳ `priority` | string | Ticket priority | +| ↳ `category` | string | Ticket category | +| ↳ `subCategory` | string | Ticket sub-category | +| ↳ `classification` | string | Ticket classification | +| ↳ `channel` | string | Origin channel | +| ↳ `departmentId` | string | Department ID | +| ↳ `contactId` | string | Contact ID | +| ↳ `accountId` | string | Account ID | +| ↳ `assigneeId` | string | Assignee ID | +| ↳ `email` | string | Contact email | +| ↳ `phone` | string | Contact phone | +| ↳ `dueDate` | string | Due date | +| ↳ `responseDueDate` | string | Response due date | +| ↳ `createdTime` | string | Created timestamp | +| ↳ `modifiedTime` | string | Last modified timestamp | +| ↳ `closedTime` | string | Closed timestamp | +| ↳ `resolution` | string | Resolution text | +| ↳ `threadCount` | string | Number of threads | +| ↳ `commentCount` | string | Number of comments | +| ↳ `webUrl` | string | Web URL to the ticket | +| ↳ `isEscalated` | boolean | Whether the ticket is escalated | +| ↳ `isOverDue` | boolean | Whether the ticket is overdue | +| ↳ `isSpam` | boolean | Whether the ticket is marked spam | +| ↳ `cf` | json | Custom field values, keyed by custom field API name | +| `count` | number | Number of tickets returned | + +### `zoho_desk_get_ticket` + +Retrieve a single Zoho Desk ticket by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID to retrieve | +| `include` | string | No | Comma-separated related data to embed. Allowed: contacts, products, assignee, departments, contract, isRead, team, skills | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `ticket` | object | The ticket | +| ↳ `id` | string | Ticket ID | +| ↳ `ticketNumber` | string | Human-readable ticket number | +| ↳ `subject` | string | Ticket subject | +| ↳ `description` | string | Ticket description \(raw; may be HTML\) | +| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim | +| ↳ `status` | string | Ticket status | +| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) | +| ↳ `priority` | string | Ticket priority | +| ↳ `category` | string | Ticket category | +| ↳ `subCategory` | string | Ticket sub-category | +| ↳ `classification` | string | Ticket classification | +| ↳ `channel` | string | Origin channel | +| ↳ `departmentId` | string | Department ID | +| ↳ `contactId` | string | Contact ID | +| ↳ `accountId` | string | Account ID | +| ↳ `assigneeId` | string | Assignee ID | +| ↳ `email` | string | Contact email | +| ↳ `phone` | string | Contact phone | +| ↳ `dueDate` | string | Due date | +| ↳ `responseDueDate` | string | Response due date | +| ↳ `createdTime` | string | Created timestamp | +| ↳ `modifiedTime` | string | Last modified timestamp | +| ↳ `closedTime` | string | Closed timestamp | +| ↳ `resolution` | string | Resolution text | +| ↳ `threadCount` | string | Number of threads | +| ↳ `commentCount` | string | Number of comments | +| ↳ `webUrl` | string | Web URL to the ticket | +| ↳ `isEscalated` | boolean | Whether the ticket is escalated | +| ↳ `isOverDue` | boolean | Whether the ticket is overdue | +| ↳ `isSpam` | boolean | Whether the ticket is marked spam | +| ↳ `cf` | json | Custom field values, keyed by custom field API name | + +### `zoho_desk_update_ticket` + +Update fields on an existing Zoho Desk ticket. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID to update | +| `subject` | string | No | Ticket subject | +| `status` | string | No | Ticket status \(e.g. Open, Closed\) | +| `priority` | string | No | Ticket priority \(e.g. High\) | +| `assigneeId` | string | No | Assignee \(agent\) ID | +| `departmentId` | string | No | Department ID | +| `category` | string | No | Ticket category | +| `subCategory` | string | No | Ticket sub-category | +| `dueDate` | string | No | Due date \(ISO 8601\) | +| `description` | string | No | Ticket description | +| `resolution` | string | No | Resolution notes recorded on the ticket | +| `classification` | string | No | Ticket classification: Problem, Request, Question, or Others | +| `customFields` | json | No | Custom field values as a JSON object, keyed by custom field API name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `ticket` | object | The updated ticket | +| ↳ `id` | string | Ticket ID | +| ↳ `ticketNumber` | string | Human-readable ticket number | +| ↳ `subject` | string | Ticket subject | +| ↳ `description` | string | Ticket description \(raw; may be HTML\) | +| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim | +| ↳ `status` | string | Ticket status | +| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) | +| ↳ `priority` | string | Ticket priority | +| ↳ `category` | string | Ticket category | +| ↳ `subCategory` | string | Ticket sub-category | +| ↳ `classification` | string | Ticket classification | +| ↳ `channel` | string | Origin channel | +| ↳ `departmentId` | string | Department ID | +| ↳ `contactId` | string | Contact ID | +| ↳ `accountId` | string | Account ID | +| ↳ `assigneeId` | string | Assignee ID | +| ↳ `email` | string | Contact email | +| ↳ `phone` | string | Contact phone | +| ↳ `dueDate` | string | Due date | +| ↳ `responseDueDate` | string | Response due date | +| ↳ `createdTime` | string | Created timestamp | +| ↳ `modifiedTime` | string | Last modified timestamp | +| ↳ `closedTime` | string | Closed timestamp | +| ↳ `resolution` | string | Resolution text | +| ↳ `threadCount` | string | Number of threads | +| ↳ `commentCount` | string | Number of comments | +| ↳ `webUrl` | string | Web URL to the ticket | +| ↳ `isEscalated` | boolean | Whether the ticket is escalated | +| ↳ `isOverDue` | boolean | Whether the ticket is overdue | +| ↳ `isSpam` | boolean | Whether the ticket is marked spam | +| ↳ `cf` | json | Custom field values, keyed by custom field API name | + +### `zoho_desk_list_comments` + +List comments on a Zoho Desk ticket. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID | +| `from` | number | No | Pagination start index \(0-based\) | +| `limit` | number | No | Number of comments to return \(1-100, default 50\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `comments` | array | List of comments | +| ↳ `id` | string | Comment ID | +| ↳ `content` | string | Comment content \(raw; may be HTML\) | +| ↳ `contentType` | string | Content type \(plainText/html\) | +| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) | +| ↳ `isPublic` | boolean | Whether the comment is public | +| ↳ `commenterId` | string | Commenter ID | +| ↳ `commenter` | object | Who wrote the comment | +| ↳ `name` | string | Display name | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `type` | string | Commenter type \(AGENT/END_USER\) | +| ↳ `roleName` | string | Role name | +| ↳ `photoURL` | string | Avatar URL | +| ↳ `commentedTime` | string | Commented timestamp | +| ↳ `modifiedTime` | string | Modified timestamp | +| ↳ `attachments` | array | Comment attachments | +| ↳ `id` | string | Attachment ID | +| ↳ `name` | string | File name | +| ↳ `size` | string | File size as reported by Zoho | +| ↳ `href` | string | Download href | +| `count` | number | Number of comments returned | + +### `zoho_desk_add_comment` + +Add a comment to a Zoho Desk ticket. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID | +| `content` | string | Yes | Comment content | +| `contentType` | string | No | Content type: plainText or html. Defaults to plainText so agent-written text posts literally; pass 'html' to send markup \(Zoho's own API default is html\). | +| `isPublic` | boolean | No | Whether the comment is public | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `comment` | object | The created comment | +| ↳ `id` | string | Comment ID | +| ↳ `content` | string | Comment content \(raw; may be HTML\) | +| ↳ `contentType` | string | Content type \(plainText/html\) | +| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) | +| ↳ `isPublic` | boolean | Whether the comment is public | +| ↳ `commenterId` | string | Commenter ID | +| ↳ `commenter` | object | Who wrote the comment | +| ↳ `name` | string | Display name | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `type` | string | Commenter type \(AGENT/END_USER\) | +| ↳ `roleName` | string | Role name | +| ↳ `photoURL` | string | Avatar URL | +| ↳ `commentedTime` | string | Commented timestamp | +| ↳ `modifiedTime` | string | Modified timestamp | +| ↳ `attachments` | array | Comment attachments | +| ↳ `id` | string | Attachment ID | +| ↳ `name` | string | File name | +| ↳ `size` | string | File size as reported by Zoho | +| ↳ `href` | string | Download href | + +### `zoho_desk_list_threads` + +List conversation threads on a Zoho Desk ticket, newest first (Zoho sorts by sendDateTime descending by default). Returns a list projection: message bodies (content, summary, to/cc/bcc) come back only from Get Thread. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID | +| `from` | number | No | Pagination start index \(0-based\) | +| `limit` | number | No | Number of threads to return \(1-200, default 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `threads` | array | List of threads | +| ↳ `id` | string | Thread ID | +| ↳ `channel` | string | Thread channel | +| ↳ `direction` | string | Direction \(in/out\) | +| ↳ `content` | string | Thread content \(raw; may be HTML\) | +| ↳ `contentType` | string | Content type | +| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) | +| ↳ `summary` | string | Thread summary | +| ↳ `responderId` | string | Responder ID | +| ↳ `createdTime` | string | Created timestamp | +| ↳ `hasAttach` | boolean | Whether the thread has attachments | +| ↳ `attachmentCount` | string | Number of attachments | +| ↳ `fromEmailAddress` | string | From email address | +| ↳ `to` | string | To email address | +| ↳ `cc` | string | CC email address | +| ↳ `bcc` | string | BCC email address | +| ↳ `replyTo` | string | Reply-to email address | +| ↳ `isForward` | boolean | Whether the thread is a forward | +| ↳ `isContentTruncated` | boolean | Whether Zoho truncated the thread content; fetch fullContentURL for the rest | +| ↳ `fullContentURL` | string | URL returning the untruncated thread content | +| ↳ `plainText` | string | Zoho's own plain-text rendering of the thread, when it supplies one | +| ↳ `status` | string | Delivery status of an outgoing thread \(SUCCESS/FAILED/DRAFT\) | +| ↳ `isDescriptionThread` | boolean | Whether this thread is the ticket's original description | +| ↳ `visibility` | string | Thread visibility \(e.g. public\) | +| ↳ `canReply` | boolean | Whether the thread can be replied to | +| ↳ `author` | object | Who sent the thread | +| ↳ `name` | string | Display name | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `type` | string | Author type \(AGENT/END_USER\) | +| ↳ `photoURL` | string | Avatar URL | +| ↳ `attachments` | array | Thread attachments | +| ↳ `id` | string | Attachment ID | +| ↳ `name` | string | File name | +| ↳ `size` | string | File size as reported by Zoho | +| ↳ `href` | string | Download href | +| `count` | number | Number of threads returned | + +### `zoho_desk_get_thread` + +Retrieve the full content of a single Zoho Desk ticket thread. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `ticketId` | string | Yes | Ticket ID | +| `threadId` | string | Yes | Thread ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `thread` | object | The thread | +| ↳ `id` | string | Thread ID | +| ↳ `channel` | string | Thread channel | +| ↳ `direction` | string | Direction \(in/out\) | +| ↳ `content` | string | Thread content \(raw; may be HTML\) | +| ↳ `contentType` | string | Content type | +| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) | +| ↳ `summary` | string | Thread summary | +| ↳ `responderId` | string | Responder ID | +| ↳ `createdTime` | string | Created timestamp | +| ↳ `hasAttach` | boolean | Whether the thread has attachments | +| ↳ `attachmentCount` | string | Number of attachments | +| ↳ `fromEmailAddress` | string | From email address | +| ↳ `to` | string | To email address | +| ↳ `cc` | string | CC email address | +| ↳ `bcc` | string | BCC email address | +| ↳ `replyTo` | string | Reply-to email address | +| ↳ `isForward` | boolean | Whether the thread is a forward | +| ↳ `isContentTruncated` | boolean | Whether Zoho truncated the thread content; fetch fullContentURL for the rest | +| ↳ `fullContentURL` | string | URL returning the untruncated thread content | +| ↳ `plainText` | string | Zoho's own plain-text rendering of the thread, when it supplies one | +| ↳ `status` | string | Delivery status of an outgoing thread \(SUCCESS/FAILED/DRAFT\) | +| ↳ `isDescriptionThread` | boolean | Whether this thread is the ticket's original description | +| ↳ `visibility` | string | Thread visibility \(e.g. public\) | +| ↳ `canReply` | boolean | Whether the thread can be replied to | +| ↳ `author` | object | Who sent the thread | +| ↳ `name` | string | Display name | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `type` | string | Author type \(AGENT/END_USER\) | +| ↳ `photoURL` | string | Avatar URL | +| ↳ `attachments` | array | Thread attachments | +| ↳ `id` | string | Attachment ID | +| ↳ `name` | string | File name | +| ↳ `size` | string | File size as reported by Zoho | +| ↳ `href` | string | Download href | + +### `zoho_desk_get_contact` + +Retrieve a Zoho Desk contact by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `contactId` | string | Yes | Contact ID to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | The contact | +| ↳ `id` | string | Contact ID | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Primary email | +| ↳ `secondaryEmail` | string | Secondary email | +| ↳ `phone` | string | Phone number | +| ↳ `mobile` | string | Mobile number | +| ↳ `accountId` | string | Associated account ID | +| ↳ `ownerId` | string | Owner ID | +| ↳ `type` | string | Contact type | +| ↳ `title` | string | Job title | +| ↳ `street` | string | Street | +| ↳ `city` | string | City | +| ↳ `state` | string | State | +| ↳ `country` | string | Country | +| ↳ `zip` | string | ZIP / postal code | +| ↳ `description` | string | Description | +| ↳ `cf` | json | Custom field values, keyed by custom field API name | + +### `zoho_desk_get_attachment` + +Download a Zoho Desk ticket attachment (from its href) as a file. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | +| `orgId` | string | Yes | Zoho Desk organization ID | +| `href` | string | Yes | Attachment download href \(from a thread or comment attachment\) | +| `fileName` | string | No | Optional file name for the downloaded file | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `file` | file | The downloaded attachment file | + +### `zoho_desk_list_organizations` + +List the Zoho Desk organizations (portals) the connected account can access. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiDomain` | string | No | Zoho Desk data-center REST base URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `organizations` | array | Accessible organizations | +| ↳ `id` | string | Organization ID | +| ↳ `companyName` | string | Company name | +| ↳ `portalName` | string | Portal name | +| `count` | number | Number of organizations returned | + + + +## Triggers + +A **Trigger** is a block that starts a workflow when an event happens in this service. + +### Zoho Desk Event + +Trigger a workflow when a Zoho Desk event occurs (ticket, comment, thread, contact, agent, task, or article changes). + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `triggerCredentials` | string | Yes | This trigger creates and manages a webhook subscription in your Zoho Desk account. | +| `orgId` | project-selector | Yes | The Zoho Desk organization \(portal\) to subscribe in. | +| `manualOrgId` | string | Yes | Type an organization ID instead of picking one from the list. | +| `eventType` | string | Yes | Event | +| `triggerDepartmentIds` | string | No | Restrict events to these departments. Leave empty for all departments. | +| `fields` | string | No | For Ticket Updated: only fire when one of these fields changes \(max 5\). Previous values are included in the payload. | +| `direction` | string | No | Thread Direction | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | The Zoho Desk event type \(e.g. Ticket_Add\) | +| `eventTime` | string | Event time in milliseconds since epoch | +| `orgId` | string | Zoho Desk organization ID | +| `payload` | json | The full resource that changed \(ticket, comment, thread, etc.\). Comment and thread events gain a derived plain-text `contentText` alongside the raw `content` + `contentType`; ticket events gain `descriptionText` alongside `description`. | +| `prevState` | json | Previous state of the resource \(update events only\) | + diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index 6b5c2adad6d..302898717d0 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -20,12 +20,16 @@ import { resolveOAuthAccountId, resolveServiceAccountToken, } from '@/app/api/auth/oauth/utils' +import { extractZohoDeskBaseFromScope } from '@/tools/zoho_desk/host-allowlist' export const dynamic = 'force-dynamic' const logger = createLogger('OAuthTokenAPI') const SALESFORCE_INSTANCE_URL_REGEX = /__sf_instance__:([^\s]+)/ +// Stop at a comma or whitespace: better-auth persists Zoho's scopes comma-joined +// (no spaces), so a greedy `\S+` would swallow the whole scope list into the host. +// The Desk base URL itself never contains a comma or space. /** * Get an access token for a specific credential @@ -182,6 +186,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { cloudId: result.cloudId, domain: result.domain, instanceUrl: result.instanceUrl, + apiDomain: result.apiDomain, authStyle: result.authStyle, }, { status: 200 } @@ -290,11 +295,23 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } + // Zoho Desk persists its data-center-specific REST base URL in the scope + // string (derived from the token response api_domain) so callers never + // assume a host. Surface it as apiDomain for tool param injection. + let apiDomain: string | undefined + if (credential.providerId === 'zoho-desk' && credential.scope) { + // Use the shared extractor, not a local regex: it also enforces https + + // the Zoho apex allowlist. This value is injected into EVERY tool call, + // so an unvalidated host here would receive the OAuth token. + apiDomain = extractZohoDeskBaseFromScope(credential.scope) + } + return NextResponse.json( { accessToken, idToken: credential.idToken || undefined, ...(instanceUrl && { instanceUrl }), + ...(apiDomain && { apiDomain }), }, { status: 200 } ) @@ -402,11 +419,23 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } } + // Zoho Desk persists its data-center-specific REST base URL in the scope + // string (derived from the token response api_domain) so callers never + // assume a host. Surface it as apiDomain for tool param injection. + let apiDomain: string | undefined + if (credential.providerId === 'zoho-desk' && credential.scope) { + // Use the shared extractor, not a local regex: it also enforces https + + // the Zoho apex allowlist. This value is injected into EVERY tool call, + // so an unvalidated host here would receive the OAuth token. + apiDomain = extractZohoDeskBaseFromScope(credential.scope) + } + return NextResponse.json( { accessToken, idToken: credential.idToken || undefined, ...(instanceUrl && { instanceUrl }), + ...(apiDomain && { apiDomain }), }, { status: 200 } ) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 7f6fe1eb461..b004e1d3148 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -354,6 +354,11 @@ export interface ServiceAccountTokenResult { domain?: string /** Salesforce only — the org's instance URL the token must be used against. */ instanceUrl?: string + /** + * Zoho Desk only — the data-center-scoped Desk REST base the token must be + * used against, forwarded to tools as their `apiDomain` param. + */ + apiDomain?: string /** * Set when the token must be sent in an `x-api-token` header instead of * `Authorization: Bearer` (e.g. Pipedrive personal API tokens). Absent means @@ -397,6 +402,8 @@ interface CachedClientCredentialToken { secretFingerprint: string /** Salesforce only — the instance URL returned alongside the minted token. */ instanceUrl?: string + /** Zoho Desk only — the Desk REST base derived from the token's api_domain. */ + apiDomain?: string } interface FailedClientCredentialMint { @@ -488,7 +495,11 @@ async function resolveClientCredentialAccountToken( cached.secretFingerprint === secretFingerprint && cached.expiresAtMs - Date.now() > CLIENT_CREDENTIAL_TOKEN_MIN_TTL_MS ) { - return { accessToken: cached.accessToken, instanceUrl: cached.instanceUrl } + return { + accessToken: cached.accessToken, + instanceUrl: cached.instanceUrl, + apiDomain: cached.apiDomain, + } } const failed = clientCredentialMintFailureCache.get(credentialId) @@ -514,6 +525,7 @@ async function resolveClientCredentialAccountToken( clientId: blob.clientId, clientSecret: blob.clientSecret, orgId: blob.orgId, + dataCenter: blob.dataCenter, }, { skipIdentity: true } ) @@ -522,8 +534,13 @@ async function resolveClientCredentialAccountToken( expiresAtMs: Date.now() + mint.expiresInSeconds * 1000, secretFingerprint, instanceUrl: mint.instanceUrl, + apiDomain: mint.apiDomain, }) - return { accessToken: mint.accessToken, instanceUrl: mint.instanceUrl } + return { + accessToken: mint.accessToken, + instanceUrl: mint.instanceUrl, + apiDomain: mint.apiDomain, + } } catch (error) { clientCredentialMintFailureCache.set(credentialId, { error, diff --git a/apps/sim/app/api/credentials/[id]/route.ts b/apps/sim/app/api/credentials/[id]/route.ts index c0d72341ed5..87a16bf9a46 100644 --- a/apps/sim/app/api/credentials/[id]/route.ts +++ b/apps/sim/app/api/credentials/[id]/route.ts @@ -89,6 +89,7 @@ export const PUT = withRouteHandler( clientId: body.clientId, clientSecret: body.clientSecret, orgId: body.orgId, + dataCenter: body.dataCenter, request, }) if (!result.success) { diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 99ba00a6151..4efe507fa9a 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -318,6 +318,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { clientId, clientSecret, orgId, + dataCenter, } = parsed.data.body const workspaceAccess = await checkWorkspaceAccess(workspaceId, session.user.id) @@ -378,6 +379,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { clientId, clientSecret, orgId, + dataCenter, }) resolvedProviderId = secret.providerId resolvedAccountId = null diff --git a/apps/sim/app/api/tools/zoho_desk/agents/route.ts b/apps/sim/app/api/tools/zoho_desk/agents/route.ts new file mode 100644 index 00000000000..40aef00acad --- /dev/null +++ b/apps/sim/app/api/tools/zoho_desk/agents/route.ts @@ -0,0 +1,147 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { zohoDeskAgentsSelectorContract } from '@/lib/api/contracts/selectors' +import { parseRequest } from '@/lib/api/server' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { resolveZohoDeskSelectorCredential } from '@/app/api/tools/zoho_desk/selector-credential' +import { assertZohoUrl } from '@/tools/zoho_desk/host-allowlist' +import { buildZohoDeskHeaders, getZohoDeskErrorMessage } from '@/tools/zoho_desk/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('ZohoDeskAgentsAPI') + +/** + * `GET /api/v1/agents` is index-paginated exactly like `/departments`: `from` is + * an index offset and `limit` caps at 200 (default 10). A short page means the + * list is exhausted. The page cap bounds the drain so a provider that keeps + * returning full pages cannot loop forever. + */ +const AGENT_PAGE_SIZE = 200 +const MAX_AGENT_PAGES = 20 + +/** + * Only active agents can own a ticket, so a disabled or deleted agent in the + * picker would only produce an assignment the API rejects. + */ +const AGENT_STATUS = 'ACTIVE' + +interface ZohoAgent { + id?: string | number + name?: string + firstName?: string + lastName?: string + emailId?: string +} + +/** + * Zoho returns `name` for most agents but leaves it (and `firstName`) empty on + * some rows, so fall back through the name parts and finally the email before + * showing a bare numeric id the user cannot recognize. + */ +function getAgentLabel(agent: ZohoAgent): string { + if (agent.name?.trim()) return agent.name.trim() + const fullName = [agent.firstName, agent.lastName] + .map((part) => part?.trim()) + .filter(Boolean) + .join(' ') + if (fullName) return fullName + return agent.emailId?.trim() || String(agent.id) +} + +/** Backs the `zoho_desk.agents` selector. */ +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + const parsed = await parseRequest(zohoDeskAgentsSelectorContract, request, {}) + if (!parsed.success) return parsed.response + const { credential, workflowId, orgId } = parsed.data.body + + const resolved = await resolveZohoDeskSelectorCredential(request, { + credentialId: credential, + workflowId, + requestId, + }) + if (!resolved.ok) return resolved.response + const { accessToken, apiBase } = resolved.credential + + const headers = buildZohoDeskHeaders({ accessToken, orgId }) + const agents: Array<{ id: string; name: string }> = [] + // Zoho's docs disagree on whether `from` is 0- or 1-based (pagination section + // says "range 0-4999, default 0"; listing examples read as 1-based). Deduping + // by id is correct under both, so the drain never yields a repeated agent. + const seenIds = new Set() + + try { + for (let page = 0; page < MAX_AGENT_PAGES; page++) { + let agentsUrl: URL + try { + agentsUrl = assertZohoUrl(`${apiBase}/agents`) + } catch { + return NextResponse.json( + { error: 'Credential resolved to a non-Zoho host' }, + { status: 400 } + ) + } + agentsUrl.searchParams.set('from', String(page * AGENT_PAGE_SIZE)) + agentsUrl.searchParams.set('limit', String(AGENT_PAGE_SIZE)) + agentsUrl.searchParams.set('status', AGENT_STATUS) + + // Same rationale as the organizations/departments/attachment routes: pin + // the resolved IP, block private/reserved hops, and drop the token if a + // Zoho-side redirect leaves the original origin. + const response = await secureFetchWithValidation(agentsUrl.toString(), { + method: 'GET', + headers, + timeout: 15_000, + stripAuthOnRedirect: true, + }) + + const body: { data?: unknown } = await response + .json() + .then((json) => (json && typeof json === 'object' ? (json as { data?: unknown }) : {})) + .catch(() => ({})) + + // Zoho answers 204 with no body once the offset runs past the last agent, + // which is a successful end-of-list, not an error. + if (response.status === 204) break + + if (!response.ok) { + const message = getZohoDeskErrorMessage( + body, + `Failed to list agents (HTTP ${response.status})` + ) + logger.warn('Failed to list Zoho Desk agents', { status: response.status, message }) + return NextResponse.json( + { error: message }, + { status: response.status >= 400 && response.status < 500 ? response.status : 502 } + ) + } + + const pageItems = Array.isArray(body.data) ? (body.data as ZohoAgent[]) : [] + for (const agent of pageItems) { + if (agent.id === undefined || agent.id === null) continue + const id = String(agent.id) + if (seenIds.has(id)) continue + seenIds.add(id) + agents.push({ id, name: getAgentLabel(agent) }) + } + + if (pageItems.length < AGENT_PAGE_SIZE) break + if (page === MAX_AGENT_PAGES - 1) { + logger.warn('Zoho Desk agents listing hit the page cap; list may be incomplete', { + pages: MAX_AGENT_PAGES, + }) + } + } + + return NextResponse.json({ agents }) + } catch (error) { + const message = getErrorMessage(error, 'Failed to list agents') + logger.error('Error listing Zoho Desk agents', { error: message }) + return NextResponse.json({ error: message }, { status: 502 }) + } +}) diff --git a/apps/sim/app/api/tools/zoho_desk/attachment/route.ts b/apps/sim/app/api/tools/zoho_desk/attachment/route.ts new file mode 100644 index 00000000000..0104619fad9 --- /dev/null +++ b/apps/sim/app/api/tools/zoho_desk/attachment/route.ts @@ -0,0 +1,143 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { zohoDeskGetAttachmentContract } from '@/lib/api/contracts/tools/zoho-desk' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { isZohoHost } from '@/tools/zoho_desk/host-allowlist' +import { + buildZohoDeskHeaders, + deriveAttachmentName, + getZohoDeskApiBase, + resolveZohoAttachmentUrl, +} from '@/tools/zoho_desk/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('ZohoDeskAttachmentAPI') + +/** + * Ceiling on a downloaded attachment. + * + * This route returns the file base64-encoded inside its JSON body, and the + * executor reads an internal tool response through `readToolResponseBody`, which + * caps at `MAX_TOOL_RESPONSE_BODY_BYTES` (10 MB). Base64 inflates by 4/3, so the + * largest attachment that can actually survive the round trip is ~7.5 MB of raw + * bytes. A larger ceiling here is not merely useless - it is actively harmful: + * the route would download, encode, and serialize the whole file (peaking around + * 250 MB of live allocation for a 50 MB attachment, with nothing limiting + * concurrent downloads) only for the executor to reject the oversized body + * afterwards. Capping at the reachable size makes the transport limit enforce + * itself early, while the bytes are still being streamed. + * + * Raising this requires uploading in the route and returning a file reference + * instead of inline base64, the way the WhatsApp media and Typeform file routes + * do - not a bigger number here. + */ +const MAX_ATTACHMENT_BYTES = 7 * 1024 * 1024 + +export const POST = withRouteHandler(async (request: NextRequest) => { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseRequest(zohoDeskGetAttachmentContract, request, {}) + if (!parsed.success) return parsed.response + const { accessToken, apiDomain, orgId, href, fileName } = parsed.data.body + + let downloadUrl: URL + try { + downloadUrl = resolveZohoAttachmentUrl( + href, + getZohoDeskApiBase({ apiDomain: apiDomain ?? undefined }) + ) + } catch { + return NextResponse.json({ success: false, error: 'Invalid attachment href' }, { status: 400 }) + } + + if (downloadUrl.protocol !== 'https:' || !isZohoHost(downloadUrl.hostname)) { + return NextResponse.json( + { success: false, error: 'Attachment href must be an https Zoho URL' }, + { status: 400 } + ) + } + + try { + // Even though the initial host is allowlisted, the download URL is + // user/LLM-influenced and Zoho may redirect. secureFetchWithValidation pins + // the resolved IP, blocks private/reserved targets on every hop, and + // (stripAuthOnRedirect) drops the OAuth token if a redirect leaves the + // original origin, so the credential never reaches an untrusted host. + // maxResponseBytes enforces the size cap while streaming. + const response = await secureFetchWithValidation(downloadUrl.toString(), { + method: 'GET', + headers: buildZohoDeskHeaders({ accessToken, orgId }), + timeout: 30_000, + maxResponseBytes: MAX_ATTACHMENT_BYTES, + stripAuthOnRedirect: true, + }) + + if (!response.ok) { + logger.warn('Failed to download Zoho Desk attachment', { status: response.status }) + return NextResponse.json( + { success: false, error: `Failed to download attachment (HTTP ${response.status})` }, + { status: response.status >= 400 && response.status < 500 ? response.status : 502 } + ) + } + + // A 204 (or any non-200 success) carries no body, so arrayBuffer() would + // yield zero bytes and the route would report success with an empty file. + if (response.status !== 200) { + return NextResponse.json( + { success: false, error: `Attachment returned no content (HTTP ${response.status})` }, + { status: 502 } + ) + } + + const arrayBuffer = await response.arrayBuffer() + + // ToolFileData (consumed by FileToolProcessor) keys the file name as `name`. + const name = deriveAttachmentName( + fileName, + response.headers.get('content-disposition'), + downloadUrl.pathname + ) + const mimeType = response.headers.get('content-type') || 'application/octet-stream' + + return NextResponse.json({ + success: true, + output: { + file: { + data: Buffer.from(arrayBuffer).toString('base64'), + mimeType, + name, + }, + }, + }) + } catch (error) { + // An oversized attachment is a client-visible limit, not a server fault - + // surface it as 413 with the actual ceiling, mirroring the WhatsApp media + // route, instead of collapsing it into a generic 500. + if (isPayloadSizeLimitError(error)) { + logger.warn('Zoho Desk attachment exceeds the download limit', { + maxBytes: MAX_ATTACHMENT_BYTES, + }) + return NextResponse.json( + { + success: false, + error: `Attachment exceeds the ${Math.floor(MAX_ATTACHMENT_BYTES / (1024 * 1024))} MB download limit`, + }, + { status: 413 } + ) + } + logger.error('Error downloading Zoho Desk attachment', { error: getErrorMessage(error) }) + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Failed to download attachment') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/zoho_desk/departments/route.ts b/apps/sim/app/api/tools/zoho_desk/departments/route.ts new file mode 100644 index 00000000000..3edb135d314 --- /dev/null +++ b/apps/sim/app/api/tools/zoho_desk/departments/route.ts @@ -0,0 +1,136 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { zohoDeskDepartmentsSelectorContract } from '@/lib/api/contracts/selectors' +import { parseRequest } from '@/lib/api/server' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { resolveZohoDeskSelectorCredential } from '@/app/api/tools/zoho_desk/selector-credential' +import { assertZohoUrl } from '@/tools/zoho_desk/host-allowlist' +import { buildZohoDeskHeaders, getZohoDeskErrorMessage } from '@/tools/zoho_desk/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('ZohoDeskDepartmentsAPI') + +/** + * `GET /api/v1/departments` is index-paginated with `limit` capped at 200 + * (default 10). A short page means the list is exhausted. + * + * Zoho's docs contradict themselves on whether `from` is 0- or 1-based: the + * pagination section documents "range 0-4999, default 0", while the listing + * examples read "from=5 and limit=50 retrieves records 5 to 54" (1-based). Under + * the 1-based reading, stepping by exactly PAGE_SIZE re-fetches the boundary + * record. Rather than guess a base we cannot confirm without a live tenant, the + * accumulator dedupes by id, which is correct under BOTH readings — the worst + * case is one redundant record per page boundary, never a duplicate entry or a + * skipped one. + * + * The page cap bounds the drain so a provider that keeps returning full pages + * cannot loop forever — 20 x 200 covers any realistic Desk portal and keeps the + * maximum `from` inside Zoho's documented 4999 ceiling. + */ +const DEPARTMENT_PAGE_SIZE = 200 +const MAX_DEPARTMENT_PAGES = 20 + +interface ZohoDepartment { + id?: string | number + name?: string + nameInCustomerPortal?: string +} + +/** Backs the `zoho_desk.departments` selector. */ +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + const parsed = await parseRequest(zohoDeskDepartmentsSelectorContract, request, {}) + if (!parsed.success) return parsed.response + const { credential, workflowId, orgId } = parsed.data.body + + const resolved = await resolveZohoDeskSelectorCredential(request, { + credentialId: credential, + workflowId, + requestId, + }) + if (!resolved.ok) return resolved.response + const { accessToken, apiBase } = resolved.credential + + const headers = buildZohoDeskHeaders({ accessToken, orgId }) + const departments: Array<{ id: string; name: string }> = [] + const seenIds = new Set() + + try { + for (let page = 0; page < MAX_DEPARTMENT_PAGES; page++) { + let departmentsUrl: URL + try { + departmentsUrl = assertZohoUrl(`${apiBase}/departments`) + } catch { + return NextResponse.json( + { error: 'Credential resolved to a non-Zoho host' }, + { status: 400 } + ) + } + departmentsUrl.searchParams.set('from', String(page * DEPARTMENT_PAGE_SIZE)) + departmentsUrl.searchParams.set('limit', String(DEPARTMENT_PAGE_SIZE)) + + // Same rationale as the organizations/attachment routes: pin the resolved + // IP, block private/reserved hops, and drop the token if a Zoho-side + // redirect leaves the original origin. + const response = await secureFetchWithValidation(departmentsUrl.toString(), { + method: 'GET', + headers, + timeout: 15_000, + stripAuthOnRedirect: true, + }) + + const body: { data?: unknown } = await response + .json() + .then((json) => (json && typeof json === 'object' ? (json as { data?: unknown }) : {})) + .catch(() => ({})) + + // Zoho answers 204 with no body once the offset runs past the last + // department, which is a successful end-of-list, not an error. + if (response.status === 204) break + + if (!response.ok) { + const message = getZohoDeskErrorMessage( + body, + `Failed to list departments (HTTP ${response.status})` + ) + logger.warn('Failed to list Zoho Desk departments', { status: response.status, message }) + return NextResponse.json( + { error: message }, + { status: response.status >= 400 && response.status < 500 ? response.status : 502 } + ) + } + + const pageItems = Array.isArray(body.data) ? (body.data as ZohoDepartment[]) : [] + for (const department of pageItems) { + if (department.id === undefined || department.id === null) continue + const id = String(department.id) + // Dedupe: see the pagination note above — a 1-based `from` would repeat + // the boundary record on every page after the first. + if (seenIds.has(id)) continue + seenIds.add(id) + departments.push({ + id, + name: department.name || department.nameInCustomerPortal || String(department.id), + }) + } + + if (pageItems.length < DEPARTMENT_PAGE_SIZE) break + if (page === MAX_DEPARTMENT_PAGES - 1) { + logger.warn('Zoho Desk departments listing hit the page cap; list may be incomplete', { + pages: MAX_DEPARTMENT_PAGES, + }) + } + } + + return NextResponse.json({ departments }) + } catch (error) { + const message = getErrorMessage(error, 'Failed to list departments') + logger.error('Error listing Zoho Desk departments', { error: message }) + return NextResponse.json({ error: message }, { status: 502 }) + } +}) diff --git a/apps/sim/app/api/tools/zoho_desk/organizations/route.ts b/apps/sim/app/api/tools/zoho_desk/organizations/route.ts new file mode 100644 index 00000000000..907d7030d94 --- /dev/null +++ b/apps/sim/app/api/tools/zoho_desk/organizations/route.ts @@ -0,0 +1,109 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { zohoDeskOrganizationsSelectorContract } from '@/lib/api/contracts/selectors' +import { parseRequest } from '@/lib/api/server' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { resolveZohoDeskSelectorCredential } from '@/app/api/tools/zoho_desk/selector-credential' +import { assertZohoUrl } from '@/tools/zoho_desk/host-allowlist' +import { getZohoDeskErrorMessage } from '@/tools/zoho_desk/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('ZohoDeskOrganizationsAPI') + +interface ZohoOrganization { + id?: string | number + companyName?: string + portalName?: string +} + +/** Backs the `zoho_desk.organizations` selector. */ +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + const parsed = await parseRequest(zohoDeskOrganizationsSelectorContract, request, {}) + if (!parsed.success) return parsed.response + const { credential, workflowId } = parsed.data.body + + const resolved = await resolveZohoDeskSelectorCredential(request, { + credentialId: credential, + workflowId, + requestId, + }) + if (!resolved.ok) return resolved.response + const { accessToken, apiBase } = resolved.credential + + // apiBase is already anchored by getZohoDeskApiBase; assert again so the URL + // that finally receives the OAuth token is validated at the point of use. + let organizationsUrl: URL + try { + organizationsUrl = assertZohoUrl(`${apiBase}/organizations`) + // Deliberately sends NO query parameters. Zoho's `/organizations` doc block + // lists none at all - not `from`, not `limit` - and its own sample is a bare + // GET. An earlier revision passed `limit=200` by extrapolating from + // /departments and /agents, but the other siblings (/tickets, /contacts, + // /comments) cap at 100 and Zoho answers an out-of-range value with 422 + // INVALID_DATA. Since `orgId` gates every tool and both other selectors, a + // 422 here would make the whole integration unreachable except through the + // manual field - a far worse failure than the known downside of sending + // nothing, which is Zoho's default page size (10 portals). + } catch { + return NextResponse.json({ error: 'Credential resolved to a non-Zoho host' }, { status: 400 }) + } + + try { + // The organizations endpoint is the one Desk call that does not require an + // orgId header, so it can bootstrap the organization selector before a + // portal has been chosen. + // Mirrors the attachment route: the initial host is allowlisted, but a + // Zoho-side redirect would otherwise be followed with the OAuth token + // attached and no IP pinning. secureFetchWithValidation pins the resolved + // IP, blocks private/reserved targets on every hop, and drops the token if + // a redirect leaves the original origin. + const response = await secureFetchWithValidation(organizationsUrl.toString(), { + method: 'GET', + headers: { + Authorization: `Zoho-oauthtoken ${accessToken}`, + 'Content-Type': 'application/json', + }, + timeout: 15_000, + stripAuthOnRedirect: true, + }) + + // secureFetchWithValidation types the body as `unknown`; Zoho wraps the list + // in `{ data: [...] }`, which is narrowed below before use. + const data: { data?: unknown } = await response + .json() + .then((body) => (body && typeof body === 'object' ? (body as { data?: unknown }) : {})) + .catch(() => ({})) + if (!response.ok) { + // Surface the failure instead of returning an empty 200, which would make + // the org dropdown silently render empty on an auth/connectivity error. + const message = getZohoDeskErrorMessage( + data, + `Failed to list organizations (HTTP ${response.status})` + ) + logger.warn('Failed to list Zoho Desk organizations', { status: response.status, message }) + return NextResponse.json( + { error: message }, + { status: response.status >= 400 && response.status < 500 ? response.status : 502 } + ) + } + + const organizations = (Array.isArray(data.data) ? (data.data as ZohoOrganization[]) : []) + .filter((org) => org.id !== undefined && org.id !== null) + .map((org) => ({ + id: String(org.id), + name: org.companyName || org.portalName || String(org.id), + })) + + return NextResponse.json({ organizations }) + } catch (error) { + const message = getErrorMessage(error, 'Failed to list organizations') + logger.error('Error listing Zoho Desk organizations', { error: message }) + return NextResponse.json({ error: message }, { status: 502 }) + } +}) diff --git a/apps/sim/app/api/tools/zoho_desk/selector-credential.ts b/apps/sim/app/api/tools/zoho_desk/selector-credential.ts new file mode 100644 index 00000000000..dbffa284a14 --- /dev/null +++ b/apps/sim/app/api/tools/zoho_desk/selector-credential.ts @@ -0,0 +1,95 @@ +import { db } from '@sim/db' +import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { eq } from 'drizzle-orm' +import { type NextRequest, NextResponse } from 'next/server' +import { authorizeCredentialUse } from '@/lib/auth/credential-access' +import { resolveCredentialAccessToken, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils' +import { extractZohoDeskBaseFromScope } from '@/tools/zoho_desk/host-allowlist' +import { getZohoDeskApiBase } from '@/tools/zoho_desk/utils' + +const logger = createLogger('ZohoDeskSelectorCredential') + +interface ResolvedZohoDeskCredential { + accessToken: string + /** Desk REST base including the `/api/v1` suffix, anchored to the Zoho apex allowlist. */ + apiBase: string +} + +type ResolveResult = + | { ok: true; credential: ResolvedZohoDeskCredential } + | { ok: false; response: NextResponse } + +/** + * Resolve a Zoho Desk credential id into an access token plus the data-center + * Desk REST base, for the selector routes. + * + * Both credential kinds are covered by one path: + * - `zoho-desk-service-account` (client credentials): the minter returns the + * data center's Desk base as `apiDomain` on every mint, so it comes straight + * off the token result. + * - OAuth connection: the token exchange persists the derived Desk base in the + * credential's scope string, so it is read back from the `account` row. + * + * The token never leaves the server — unlike the previous combobox, which + * fetched it into the browser before posting it back. + */ +export async function resolveZohoDeskSelectorCredential( + request: NextRequest, + params: { credentialId: string; workflowId?: string; requestId: string } +): Promise { + const { credentialId, workflowId, requestId } = params + + const authz = await authorizeCredentialUse(request, { credentialId, workflowId }) + if (!authz.ok || !authz.credentialOwnerUserId) { + return { + ok: false, + response: NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }), + } + } + + const tokenResult = await resolveCredentialAccessToken( + credentialId, + authz.credentialOwnerUserId, + requestId + ) + if (!tokenResult?.accessToken) { + logger.error('Failed to get Zoho Desk access token', { credentialId }) + return { + ok: false, + response: NextResponse.json( + { error: 'Could not retrieve access token', authRequired: true }, + { status: 401 } + ), + } + } + + // Service-account mints carry `apiDomain`; an OAuth connection stores the same + // value on its account row instead. Falling through to `undefined` lets + // getZohoDeskApiBase apply the US default rather than guessing a host. + const apiDomain = tokenResult.apiDomain ?? (await readOAuthApiDomain(credentialId)) + + return { + ok: true, + credential: { + accessToken: tokenResult.accessToken, + apiBase: getZohoDeskApiBase({ apiDomain }), + }, + } +} + +async function readOAuthApiDomain(credentialId: string): Promise { + try { + const resolved = await resolveOAuthAccountId(credentialId) + if (!resolved?.accountId) return undefined + const [row] = await db + .select({ scope: account.scope }) + .from(account) + .where(eq(account.id, resolved.accountId)) + .limit(1) + return extractZohoDeskBaseFromScope(row?.scope) + } catch (error) { + logger.warn('Failed to resolve Zoho Desk data center from credential', { error }) + return undefined + } +} diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx index 1d7f000d371..1d6d0c014d0 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx @@ -35,7 +35,10 @@ function messageForClientCredentialError( descriptor: ClientCredentialAccountDescriptor ): string { if (isApiClientError(err) && err.code) { - const fieldLabels = descriptor.fields.map((field) => field.label).join(', ') + const fieldLabels = descriptor.fields + .filter((field) => !field.optional) + .map((field) => field.label) + .join(', ') switch (err.code) { case 'invalid_credentials': return `We couldn't authenticate with those credentials. Check that the ${fieldLabels} all belong to the same ${descriptor.serviceLabel} app and that the app is authorized.` @@ -92,6 +95,7 @@ export function ClientCredentialAccountModal({ const [clientId, setClientId] = useState('') const [clientSecret, setClientSecret] = useState('') const [orgId, setOrgId] = useState('') + const [dataCenter, setDataCenter] = useState('') const [displayName, setDisplayName] = useState(initialDisplayName ?? '') const [description, setDescription] = useState(initialDescription ?? '') const [error, setError] = useState(null) @@ -104,6 +108,7 @@ export function ClientCredentialAccountModal({ setClientId('') setClientSecret('') setOrgId('') + setDataCenter('') setDisplayName(initialDisplayName ?? '') setDescription(initialDescription ?? '') setError(null) @@ -112,10 +117,12 @@ export function ClientCredentialAccountModal({ const clientIdField = descriptor.fields.find((field) => field.id === 'clientId') const clientSecretField = descriptor.fields.find((field) => field.id === 'clientSecret') const orgIdField = descriptor.fields.find((field) => field.id === 'orgId') + const dataCenterField = descriptor.fields.find((field) => field.id === 'dataCenter') const trimmedClientId = clientId.trim() const trimmedClientSecret = clientSecret.trim() const trimmedOrgId = orgId.trim() + const trimmedDataCenter = dataCenter.trim() const isPending = createCredential.isPending || updateCredential.isPending const isDisabled = !trimmedClientId || !trimmedClientSecret || !trimmedOrgId || isPending @@ -136,6 +143,7 @@ export function ClientCredentialAccountModal({ clientId: trimmedClientId, clientSecret: trimmedClientSecret, orgId: trimmedOrgId, + dataCenter: trimmedDataCenter || undefined, } if (credentialId) { await updateCredential.mutateAsync({ @@ -227,6 +235,21 @@ export function ClientCredentialAccountModal({ /> )} + {dataCenterField && ( + { + setDataCenter(value) + if (error) setError(null) + }} + placeholder={dataCenterField.placeholder} + autoComplete='off' + hint={hintFor(dataCenterField, trimmedDataCenter) ?? dataCenterField.hintMessage} + /> + )} + = { + type: 'zoho_desk', + name: 'Zoho Desk', + description: 'Manage Zoho Desk tickets, comments, threads, and contacts', + authMode: AuthMode.OAuth, + triggerAllowed: true, + longDescription: + 'Read and update Zoho Desk tickets, manage comments and threads, look up contacts, and download attachments. Can also trigger workflows from Zoho Desk webhook events.', + docsLink: 'https://docs.sim.ai/integrations/zoho_desk', + category: 'tools', + integrationType: IntegrationType.Support, + bgColor: '#E42527', + icon: ZohoDeskIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Tickets', id: 'list_tickets' }, + { label: 'Get Ticket', id: 'get_ticket' }, + { label: 'Update Ticket', id: 'update_ticket' }, + { label: 'List Comments', id: 'list_comments' }, + { label: 'Add Comment', id: 'add_comment' }, + { label: 'List Threads', id: 'list_threads' }, + { label: 'Get Thread', id: 'get_thread' }, + { label: 'Get Contact', id: 'get_contact' }, + { label: 'Get Attachment', id: 'get_attachment' }, + { label: 'List Organizations', id: 'list_organizations' }, + ], + value: () => 'list_tickets', + }, + { + id: 'credential', + title: 'Zoho Desk Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + required: true, + serviceId: 'zoho-desk', + requiredScopes: getScopesForService('zoho-desk'), + placeholder: 'Select Zoho Desk account', + }, + { + id: 'manualCredential', + title: 'Zoho Desk Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + { + id: 'orgId', + title: 'Organization', + type: 'project-selector', + canonicalParamId: 'orgId', + serviceId: 'zoho-desk', + selectorKey: 'zoho_desk.organizations', + placeholder: 'Select an organization', + dependsOn: ['credential'], + mode: 'basic', + condition: { field: 'operation', value: OPERATIONS_NEEDING_ORG }, + required: { field: 'operation', value: OPERATIONS_NEEDING_ORG }, + }, + { + id: 'manualOrgId', + title: 'Organization ID', + type: 'short-input', + canonicalParamId: 'orgId', + placeholder: 'Enter organization ID', + mode: 'advanced', + condition: { field: 'operation', value: OPERATIONS_NEEDING_ORG }, + required: { field: 'operation', value: OPERATIONS_NEEDING_ORG }, + }, + // Ticket ID (shared by several operations) + { + id: 'ticketId', + title: 'Ticket ID', + type: 'short-input', + placeholder: 'Enter ticket ID', + condition: { + field: 'operation', + value: [ + 'get_ticket', + 'update_ticket', + 'list_comments', + 'add_comment', + 'list_threads', + 'get_thread', + ], + }, + required: { + field: 'operation', + value: [ + 'get_ticket', + 'update_ticket', + 'list_comments', + 'add_comment', + 'list_threads', + 'get_thread', + ], + }, + }, + { + id: 'threadId', + title: 'Thread ID', + type: 'short-input', + placeholder: 'Enter thread ID', + condition: { field: 'operation', value: 'get_thread' }, + required: { field: 'operation', value: 'get_thread' }, + }, + { + id: 'contactId', + title: 'Contact ID', + type: 'short-input', + placeholder: 'Enter contact ID', + condition: { field: 'operation', value: 'get_contact' }, + required: { field: 'operation', value: 'get_contact' }, + }, + // Add comment + { + id: 'content', + title: 'Comment', + type: 'long-input', + placeholder: 'Comment content', + condition: { field: 'operation', value: 'add_comment' }, + required: { field: 'operation', value: 'add_comment' }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'Plain text', id: 'plainText' }, + { label: 'HTML', id: 'html' }, + ], + value: () => 'plainText', + mode: 'advanced', + condition: { field: 'operation', value: 'add_comment' }, + }, + { + id: 'isPublic', + title: 'Public Comment', + type: 'switch', + defaultValue: false, + condition: { field: 'operation', value: 'add_comment' }, + }, + // Update ticket + { + id: 'subject', + title: 'Subject', + type: 'short-input', + placeholder: 'New subject', + condition: { field: 'operation', value: 'update_ticket' }, + }, + // status/priority are deliberately split per operation rather than shared. + // A subBlock keeps its value when the operation changes, and the two uses are + // semantically opposite: on list_tickets they are filters (comma-separated, + // matching), on update_ticket they are the new value written to the ticket. + // Sharing one field meant a filter of "Open,On Hold" could be PATCHed onto a + // ticket, and an update value of "Closed" could silently filter a later list. + { + id: 'status', + title: 'Status', + type: 'short-input', + placeholder: 'e.g. Closed', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'priority', + title: 'Priority', + type: 'short-input', + placeholder: 'e.g. High', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'statusFilter', + title: 'Status', + type: 'short-input', + placeholder: 'Filter, e.g. Open,On Hold', + condition: { field: 'operation', value: 'list_tickets' }, + }, + { + id: 'priorityFilter', + title: 'Priority', + type: 'short-input', + placeholder: 'Filter, e.g. High,Urgent', + condition: { field: 'operation', value: 'list_tickets' }, + }, + { + id: 'assigneeId', + title: 'Assignee', + type: 'project-selector', + canonicalParamId: 'assigneeId', + serviceId: 'zoho-desk', + selectorKey: 'zoho_desk.agents', + placeholder: 'Assign the ticket to this agent', + dependsOn: ['credential', 'orgId'], + mode: 'basic', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'manualAssigneeId', + title: 'Assignee ID', + type: 'short-input', + canonicalParamId: 'assigneeId', + placeholder: 'Agent ID', + mode: 'advanced', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'description', + title: 'Description', + type: 'long-input', + placeholder: 'Replace the ticket description', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + }, + { + id: 'resolution', + title: 'Resolution', + type: 'long-input', + placeholder: 'Resolution notes', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + }, + { + id: 'classification', + title: 'Classification', + type: 'dropdown', + options: [ + { label: 'Problem', id: 'Problem' }, + { label: 'Request', id: 'Request' }, + { label: 'Question', id: 'Question' }, + { label: 'Others', id: 'Others' }, + ], + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + }, + { + id: 'departmentId', + title: 'Department', + type: 'project-selector', + canonicalParamId: 'departmentId', + serviceId: 'zoho-desk', + selectorKey: 'zoho_desk.departments', + placeholder: 'Move ticket to this department', + dependsOn: ['credential', 'orgId'], + mode: 'basic', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'manualDepartmentId', + title: 'Department ID', + type: 'short-input', + canonicalParamId: 'departmentId', + placeholder: 'Move ticket to this department', + mode: 'advanced', + condition: { field: 'operation', value: 'update_ticket' }, + }, + { + id: 'category', + title: 'Category', + type: 'short-input', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + }, + { + id: 'subCategory', + title: 'Sub-category', + type: 'short-input', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + }, + { + id: 'dueDate', + title: 'Due Date', + type: 'short-input', + placeholder: 'ISO 8601 timestamp', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.', + generationType: 'timestamp', + }, + }, + { + id: 'customFields', + title: 'Custom Fields', + type: 'long-input', + placeholder: '{"cf_severity": "High"}', + condition: { field: 'operation', value: 'update_ticket' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object mapping Zoho Desk custom field API names (they start with cf_) to values. Return ONLY the JSON object - no explanations, no extra text.', + generationType: 'json-object', + }, + }, + // Get attachment + { + id: 'href', + title: 'Attachment href', + type: 'short-input', + placeholder: 'Attachment download href from a thread or comment', + condition: { field: 'operation', value: 'get_attachment' }, + required: { field: 'operation', value: 'get_attachment' }, + }, + { + id: 'fileName', + title: 'File Name', + type: 'short-input', + placeholder: 'Optional file name', + condition: { field: 'operation', value: 'get_attachment' }, + mode: 'advanced', + }, + // Shared filters / options + { + id: 'departmentIds', + title: 'Departments', + type: 'project-selector', + canonicalParamId: 'departmentIds', + serviceId: 'zoho-desk', + selectorKey: 'zoho_desk.departments', + multiSelect: true, + placeholder: 'Filter by department', + dependsOn: ['credential', 'orgId'], + mode: 'basic', + condition: { field: 'operation', value: 'list_tickets' }, + }, + { + id: 'manualDepartmentIds', + title: 'Department IDs', + type: 'short-input', + canonicalParamId: 'departmentIds', + placeholder: 'Comma-separated department IDs', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tickets' }, + }, + { + id: 'include', + title: 'Include', + type: 'short-input', + placeholder: 'e.g. contacts,assignee', + condition: { field: 'operation', value: ['list_tickets', 'get_ticket'] }, + mode: 'advanced', + }, + { + id: 'sortBy', + title: 'Sort By', + type: 'short-input', + placeholder: 'createdTime, customerResponseTime, or responseDueDate', + condition: { field: 'operation', value: 'list_tickets' }, + mode: 'advanced', + }, + { + id: 'from', + title: 'From', + type: 'short-input', + placeholder: 'Start index (0-based)', + condition: { + field: 'operation', + value: ['list_tickets', 'list_comments', 'list_threads'], + }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Max results (tickets/comments 100, threads 200)', + condition: { + field: 'operation', + value: ['list_tickets', 'list_comments', 'list_threads'], + }, + mode: 'advanced', + }, + ...getTrigger('zoho_desk').subBlocks, + ], + tools: { + access: [ + 'zoho_desk_list_tickets', + 'zoho_desk_get_ticket', + 'zoho_desk_update_ticket', + 'zoho_desk_list_comments', + 'zoho_desk_add_comment', + 'zoho_desk_list_threads', + 'zoho_desk_get_thread', + 'zoho_desk_get_contact', + 'zoho_desk_get_attachment', + 'zoho_desk_list_organizations', + ], + config: { + tool: (params) => `zoho_desk_${params.operation}`, + params: (params) => { + // Pull raw pagination out of the spread so invalid values never reach the + // tool; only re-add them when Number() yields a finite value (a non-numeric + // typo would otherwise become NaN and produce an invalid Zoho query param). + const { + oauthCredential, + from: rawFrom, + limit: rawLimit, + contentType, + isPublic, + status: rawStatus, + priority: rawPriority, + statusFilter: rawStatusFilter, + priorityFilter: rawPriorityFilter, + customFields: rawCustomFields, + departmentIds: rawDepartmentIds, + ...rest + } = params + const result: Record = { ...rest, oauthCredential } + + // The basic-mode department picker is multi-select, so it stores an + // array while the advanced manual field stores the raw comma-separated + // string Zoho's `departmentIds` query param expects. Normalize both to + // that string so the wire format never depends on which mode was used. + const departmentIds = Array.isArray(rawDepartmentIds) + ? rawDepartmentIds + .map((id) => String(id).trim()) + .filter(Boolean) + .join(',') + : typeof rawDepartmentIds === 'string' + ? rawDepartmentIds.trim() + : '' + if (departmentIds) result.departmentIds = departmentIds + + // contentType is the comment's content type; its default would otherwise + // serialize for every operation (e.g. get_attachment, which has no such + // param). Only forward it for add_comment so the UI can't imply an option + // that has no effect elsewhere. + if (params.operation === 'add_comment' && typeof contentType === 'string' && contentType) { + result.contentType = contentType + } + + // Zoho documents from >= 0 and limit >= 1 as integers; a negative or + // fractional value reaches the API as an opaque provider error, so drop + // anything outside those bounds here rather than round-tripping it. + // `null` is checked explicitly: the serializer initializes untouched + // subBlocks to null, and Number(null) is 0 — which would otherwise inject + // from=0 on every operation instead of leaving the param unset. + if (rawFrom !== undefined && rawFrom !== null && rawFrom !== '') { + const from = Number(rawFrom) + if (Number.isInteger(from) && from >= 0) result.from = from + } + if (rawLimit !== undefined && rawLimit !== null && rawLimit !== '') { + const limit = Number(rawLimit) + if (Number.isInteger(limit) && limit >= 1) result.limit = limit + } + // Gated for the same reason as contentType above: isPublic carries a + // defaultValue, so forwarding it unconditionally would serialize a + // comment-only field onto every other operation's params. Destructured + // out of `rest` so the default never reaches non-comment operations. + if (params.operation === 'add_comment' && isPublic !== undefined) { + result.isPublic = isPublic === true || isPublic === 'true' + } + // Gated to update_ticket for the same reason as contentType and isPublic + // above: the subBlock keeps its value when the operation changes, so + // stale (or half-typed) JSON left behind after switching away from + // Update Ticket would otherwise fail every unrelated operation with + // "Invalid JSON provided for custom fields" - on runs that never send it. + // Only list_tickets and update_ticket declare status/priority. The other + // eight operations must receive neither - a ternary with a bare `else` + // would forward a stale Update Ticket value into e.g. get_ticket. + const activeStatus = + params.operation === 'list_tickets' + ? rawStatusFilter + : params.operation === 'update_ticket' + ? rawStatus + : undefined + const activePriority = + params.operation === 'list_tickets' + ? rawPriorityFilter + : params.operation === 'update_ticket' + ? rawPriority + : undefined + if (activeStatus !== undefined && activeStatus !== null && activeStatus !== '') { + result.status = activeStatus + } + if (activePriority !== undefined && activePriority !== null && activePriority !== '') { + result.priority = activePriority + } + + if (params.operation === 'update_ticket' && rawCustomFields !== undefined) { + if (typeof rawCustomFields === 'string') { + if (rawCustomFields.trim()) { + try { + result.customFields = JSON.parse(rawCustomFields) + } catch { + throw new Error('Invalid JSON provided for custom fields') + } + } + } else if (rawCustomFields !== null) { + // Already an object when an agent supplies it directly. + result.customFields = rawCustomFields + } + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Zoho Desk credential' }, + orgId: { type: 'string', description: 'Zoho Desk organization ID' }, + ticketId: { type: 'string', description: 'Ticket ID' }, + threadId: { type: 'string', description: 'Thread ID' }, + contactId: { type: 'string', description: 'Contact ID' }, + content: { type: 'string', description: 'Comment content' }, + contentType: { type: 'string', description: 'Comment content type (plainText/html)' }, + isPublic: { type: 'boolean', description: 'Whether a comment is public' }, + subject: { type: 'string', description: 'Ticket subject' }, + status: { type: 'string', description: 'Ticket status to set' }, + statusFilter: { type: 'string', description: 'Status filter for listing tickets' }, + priorityFilter: { type: 'string', description: 'Priority filter for listing tickets' }, + priority: { type: 'string', description: 'Ticket priority' }, + assigneeId: { type: 'string', description: 'Assignee (agent) ID' }, + description: { type: 'string', description: 'Ticket description' }, + resolution: { type: 'string', description: 'Resolution notes' }, + classification: { type: 'string', description: 'Ticket classification' }, + departmentId: { type: 'string', description: 'Department ID to move a ticket to' }, + departmentIds: { type: 'string', description: 'Department IDs to filter by (comma-separated)' }, + category: { type: 'string', description: 'Ticket category' }, + subCategory: { type: 'string', description: 'Ticket sub-category' }, + dueDate: { type: 'string', description: 'Ticket due date' }, + customFields: { type: 'json', description: 'Custom field values' }, + href: { type: 'string', description: 'Attachment download href' }, + fileName: { type: 'string', description: 'Downloaded file name' }, + include: { type: 'string', description: 'Related data to include' }, + sortBy: { type: 'string', description: 'Sort field' }, + from: { type: 'number', description: 'Pagination start index' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + outputs: { + tickets: { type: 'array', description: 'List of tickets' }, + ticket: { type: 'json', description: 'A single ticket' }, + comments: { type: 'array', description: 'List of comments' }, + comment: { type: 'json', description: 'A single comment' }, + threads: { type: 'array', description: 'List of threads' }, + thread: { type: 'json', description: 'A single thread' }, + contact: { type: 'json', description: 'A contact' }, + organizations: { type: 'array', description: 'Accessible organizations' }, + file: { type: 'file', description: 'Downloaded attachment file' }, + count: { type: 'number', description: 'Number of items returned' }, + }, + triggers: { + enabled: true, + available: ['zoho_desk'], + }, +} + +export const ZohoDeskBlockMeta = { + tags: ['customer-support', 'ticketing', 'automation'], + url: 'https://www.zoho.com/desk/', + templates: [ + { + icon: ZohoDeskIcon, + title: 'Zoho Desk new-ticket Slack alert', + prompt: + 'When a new ticket is created in Zoho Desk, send a formatted Slack message to my support channel with the subject, priority, requester, and a link to the ticket.', + modules: ['agent', 'workflows'], + category: 'support', + tags: ['automation', 'communication'], + alsoIntegrations: ['slack'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk AI ticket triage', + prompt: + 'When a Zoho Desk ticket is created, read the subject and description, classify the priority and category, and update the ticket with the suggested priority and a triage comment.', + modules: ['agent', 'workflows'], + category: 'support', + tags: ['automation', 'ai'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk AI draft reply', + prompt: + 'When a customer adds a new thread to a Zoho Desk ticket, fetch the full thread, draft a helpful reply grounded in my knowledge base, and post it as a private comment for an agent to review.', + modules: ['agent', 'knowledge-base', 'workflows'], + category: 'support', + tags: ['ai', 'automation'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk escalation watcher', + prompt: + 'Create a scheduled workflow that lists open high-priority Zoho Desk tickets with no agent response, and pings the on-call engineer in Slack with the ticket details.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'support', + tags: ['monitoring', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk daily support digest', + prompt: + 'Build a scheduled workflow that pulls all Zoho Desk tickets updated in the last 24 hours, summarizes volume by status and priority, and emails a digest to the support lead.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'support', + tags: ['reporting', 'automation'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk contact enrichment', + prompt: + 'When a Zoho Desk ticket is created, look up the contact, enrich it with data from our CRM, and add a comment summarizing the customer context for the agent.', + modules: ['agent', 'workflows'], + category: 'support', + tags: ['automation', 'ai'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk knowledge search', + prompt: + 'Create a knowledge base from my resolved Zoho Desk tickets and threads so I can ask an agent questions like "how did we resolve the billing sync issue?" and get answers with ticket citations.', + modules: ['knowledge-base', 'agent'], + category: 'support', + tags: ['research', 'ai'], + }, + { + icon: ZohoDeskIcon, + title: 'Zoho Desk attachment archiver', + prompt: + 'When a Zoho Desk ticket thread includes an attachment, download the file and upload it to Google Drive in a folder named after the ticket number.', + modules: ['agent', 'files', 'workflows'], + category: 'support', + tags: ['automation', 'files'], + alsoIntegrations: ['google_drive'], + }, + ], + skills: [ + { + name: 'triage-new-ticket', + description: + 'Read an incoming Zoho Desk ticket, classify it, and set priority, classification, category, and assignee.', + content: + '# Triage a Zoho Desk Ticket\n\nClassify a newly created ticket and route it to the right owner.\n\n## Steps\n1. If the organization ID is unknown, List Organizations and pick the portal to work in.\n2. Get Ticket for the ticket ID and read subject, descriptionText, channel, and status.\n3. Decide the urgency and the owning team from the content, and pick a classification of Problem, Request, Question, or Others.\n4. Update Ticket to set priority, classification, category and subCategory, and the assigneeId or departmentId that should own it.\n5. Add Comment as an internal note explaining the triage decision so the agent who picks it up has the reasoning.\n\n## Output\nReport the ticket ID and number, the classification and priority set, the assignee or department it was routed to, and anything ambiguous that needs a human decision.', + }, + { + name: 'escalate-overdue-tickets', + description: + 'Find Zoho Desk tickets past or near their response due date and escalate them with a higher priority and an owner.', + content: + '# Escalate Overdue Tickets\n\nCatch tickets that are breaching or about to breach their response commitment.\n\n## Steps\n1. List Tickets filtered to open statuses, sorted by responseDueDate so the soonest-due tickets come first. Page with from and limit if the queue is large.\n2. Compare responseDueDate and dueDate against the current time to separate already-breached tickets from ones due shortly.\n3. For each breached ticket, Update Ticket to raise priority and reassign to the escalation owner via assigneeId or departmentId.\n4. Add Comment as an internal note recording that the ticket was escalated, how overdue it was, and who now owns it.\n\n## Output\nA list of escalated tickets with ticket number, how far past due each was, the new priority, and the new owner. Include a separate at-risk list of tickets due soon but not yet breached.', + }, + { + name: 'daily-ticket-digest', + description: + 'Summarize the current Zoho Desk queue by status, priority, and age into a digest for the support team.', + content: + '# Daily Ticket Digest\n\nProduce a morning read on where the support queue stands.\n\n## Steps\n1. List Tickets for the department, filtering by the statuses you care about such as "Open,On Hold". Page with from and limit until the queue is covered.\n2. Group the results by status, priority, and assignee, and compute counts for each group.\n3. Flag unassigned tickets, tickets past responseDueDate, and anything that has sat untouched since createdTime.\n4. Write a short narrative of what changed and what needs attention today.\n\n## Output\nA digest with counts by status and priority, load per assignee, and a callout list of unassigned and overdue tickets by ticket number. State the filters used so the numbers are reproducible.', + }, + { + name: 'draft-reply-as-internal-note', + description: + 'Read a Zoho Desk ticket conversation and post a suggested reply as a private comment for an agent to review.', + content: + '# Draft a Reply as an Internal Note\n\nPrepare a response an agent can review and send, without messaging the customer directly.\n\n## Steps\n1. Get Ticket to read the subject, descriptionText, status, and priority.\n2. List Threads for the ticket and Get Thread on the most recent customer message to read its full content.\n3. List Comments to check what has already been discussed internally so the draft does not repeat prior advice.\n4. Write a reply that answers the latest customer message, and Add Comment with isPublic set to false so it posts as an internal draft. Use plainText unless the draft genuinely contains markup.\n\n## Output\nThe ticket number, the comment ID of the posted draft, and a note on any facts the draft assumes that an agent must verify before sending. This posts an internal note only, never a reply to the customer.', + }, + { + name: 'enrich-ticket-with-customer-context', + description: + 'Pull the contact behind a Zoho Desk ticket and post a customer context summary as an internal note.', + content: + '# Enrich a Ticket With Customer Context\n\nGive the assigned agent the customer background before they start working the ticket.\n\n## Steps\n1. Get Ticket with include set to contacts so the related contact record comes back with the ticket.\n2. Get Contact for the ticket contactId to read the full contact record, including accountId, job title, email, and phone.\n3. List Tickets filtered to the same department and scan for other tickets from the same contact or account to spot repeat issues.\n4. Add Comment as an internal note summarizing who the customer is, their account, and any related open or recent tickets.\n\n## Output\nThe ticket number, the contact and account identified, a list of related tickets by number, and the internal note that was posted. Say explicitly if no contact is linked to the ticket.', + }, + { + name: 'package-ticket-for-engineering', + description: + 'Assemble a Zoho Desk ticket conversation and its attachments into a bug report, then record the escalation on the ticket.', + content: + '# Package a Ticket for Engineering\n\nTurn a support ticket into a developer-ready bug report.\n\n## Steps\n1. Get Ticket to read the subject, descriptionText, priority, and classification.\n2. List Threads and Get Thread on the relevant messages to extract reproduction steps, error text, and environment details.\n3. For each attachment referenced on a thread or comment, Get Attachment with its href to download the screenshot or log.\n4. Write the bug report with a summary, steps to reproduce, expected versus actual behavior, and the attached evidence.\n5. Update Ticket to set classification to Problem and record the tracker key in a custom field via customFields, then Add Comment as an internal note linking the escalation.\n\n## Output\nThe assembled bug report, the ticket number it came from, the attachments downloaded, and confirmation of the custom field and internal note written back to the ticket.', + }, + { + name: 'ticket-knowledge-gap-report', + description: + 'Scan recent Zoho Desk tickets for recurring themes and propose the knowledge base articles that would deflect them.', + content: + '# Ticket Knowledge Gap Report\n\nFind the questions customers keep asking that no article answers.\n\n## Steps\n1. List Tickets across the statuses and departments in scope, paging with from and limit to cover a meaningful window.\n2. Read subject, descriptionText, category, and subCategory on each and group tickets into recurring themes.\n3. Rank themes by ticket volume, and for the top ones Get Ticket and List Threads on a couple of examples to understand what the customer actually needed.\n4. For each theme, propose a knowledge base article with a working title and the questions it must answer.\n\n## Output\nA ranked table of themes with ticket counts and example ticket numbers, plus the proposed article titles and outlines. Note which themes are one-off incidents rather than genuine documentation gaps.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index aa4dfac717f..4ef1bad73a3 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -338,6 +338,7 @@ import { YouTubeBlock, YouTubeBlockMeta } from '@/blocks/blocks/youtube' import { ZendeskBlock, ZendeskBlockMeta } from '@/blocks/blocks/zendesk' import { ZepBlock, ZepBlockMeta } from '@/blocks/blocks/zep' import { ZeroBounceBlock, ZeroBounceBlockMeta } from '@/blocks/blocks/zerobounce' +import { ZohoDeskBlock, ZohoDeskBlockMeta } from '@/blocks/blocks/zoho-desk' import { ZoomBlock, ZoomBlockMeta } from '@/blocks/blocks/zoom' import { ZoomInfoBlock, ZoomInfoBlockMeta } from '@/blocks/blocks/zoominfo' import type { BlockConfig, BlockMeta } from '@/blocks/types' @@ -657,6 +658,7 @@ export const BLOCK_REGISTRY: Record = { youtube: YouTubeBlock, zendesk: ZendeskBlock, zep: ZepBlock, + zoho_desk: ZohoDeskBlock, zoom: ZoomBlock, zoominfo: ZoomInfoBlock, } @@ -922,6 +924,7 @@ export const BLOCK_META_REGISTRY: Record = { zendesk: ZendeskBlockMeta, zep: ZepBlockMeta, zerobounce: ZeroBounceBlockMeta, + zoho_desk: ZohoDeskBlockMeta, zoom: ZoomBlockMeta, zoominfo: ZoomInfoBlockMeta, } diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 256501851da..7b121cbc3a0 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -8927,3 +8927,20 @@ export function LogfireIcon(props: SVGProps) { ) } + +export function ZohoDeskIcon(props: SVGProps) { + return ( + + ) +} diff --git a/apps/sim/hooks/queries/credentials.ts b/apps/sim/hooks/queries/credentials.ts index 0fee6c19a0a..25daaf06179 100644 --- a/apps/sim/hooks/queries/credentials.ts +++ b/apps/sim/hooks/queries/credentials.ts @@ -147,6 +147,7 @@ export function useUpdateWorkspaceCredential() { clientId: payload.clientId, clientSecret: payload.clientSecret, orgId: payload.orgId, + dataCenter: payload.dataCenter, }, }) }, diff --git a/apps/sim/hooks/selectors/helpers.ts b/apps/sim/hooks/selectors/helpers.ts index ca4d2151abf..47a2c068d13 100644 --- a/apps/sim/hooks/selectors/helpers.ts +++ b/apps/sim/hooks/selectors/helpers.ts @@ -5,6 +5,7 @@ export interface OAuthTokenBundle { accessToken: string cloudId?: string domain?: string + apiDomain?: string } /** @@ -24,5 +25,6 @@ export async function fetchOAuthToken( accessToken: token.accessToken, cloudId: token.cloudId, domain: token.domain, + apiDomain: token.apiDomain, } } diff --git a/apps/sim/hooks/selectors/providers/zoho-desk/selectors.test.ts b/apps/sim/hooks/selectors/providers/zoho-desk/selectors.test.ts new file mode 100644 index 00000000000..846a45a6058 --- /dev/null +++ b/apps/sim/hooks/selectors/providers/zoho-desk/selectors.test.ts @@ -0,0 +1,188 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockRequestJson } = vi.hoisted(() => ({ mockRequestJson: vi.fn() })) + +vi.mock('@/lib/api/client/request', () => ({ requestJson: mockRequestJson })) + +import { getSelectorDefinition } from '@/hooks/selectors/registry' +import type { SelectorQueryArgs } from '@/hooks/selectors/types' + +const organizations = getSelectorDefinition('zoho_desk.organizations') +const departments = getSelectorDefinition('zoho_desk.departments') +const agents = getSelectorDefinition('zoho_desk.agents') + +const orgArgs = (overrides: Partial = {}): SelectorQueryArgs => ({ + key: 'zoho_desk.organizations', + context: { oauthCredential: 'cred-1', workflowId: 'wf-1', ...overrides }, +}) + +const deptArgs = (overrides: Partial = {}): SelectorQueryArgs => ({ + key: 'zoho_desk.departments', + context: { oauthCredential: 'cred-1', workflowId: 'wf-1', orgId: 'org-9', ...overrides }, +}) + +const agentArgs = (overrides: Partial = {}): SelectorQueryArgs => ({ + key: 'zoho_desk.agents', + context: { oauthCredential: 'cred-1', workflowId: 'wf-1', orgId: 'org-9', ...overrides }, +}) + +describe('zoho_desk.organizations selector', () => { + beforeEach(() => vi.clearAllMocks()) + + it('is enabled only once a credential is selected', () => { + expect(organizations.enabled?.(orgArgs())).toBe(true) + expect(organizations.enabled?.(orgArgs({ oauthCredential: undefined }))).toBe(false) + }) + + it('keys the query by credential', () => { + expect(organizations.getQueryKey(orgArgs())).toEqual([ + 'selectors', + 'zoho_desk.organizations', + 'cred-1', + ]) + expect(organizations.getQueryKey(orgArgs({ oauthCredential: undefined }))).toEqual([ + 'selectors', + 'zoho_desk.organizations', + 'none', + ]) + }) + + it('posts the credential id (never a token) and maps organizations to options', async () => { + mockRequestJson.mockResolvedValue({ + organizations: [ + { id: '700123', name: 'Zylker' }, + { id: '700124', name: 'zPad' }, + ], + }) + + const options = await organizations.fetchList?.(orgArgs()) + + expect(mockRequestJson).toHaveBeenCalledWith( + expect.objectContaining({ path: '/api/tools/zoho_desk/organizations' }), + expect.objectContaining({ body: { credential: 'cred-1', workflowId: 'wf-1' } }) + ) + expect(options).toEqual([ + { id: '700123', label: 'Zylker' }, + { id: '700124', label: 'zPad' }, + ]) + }) + + it('throws when the credential is missing rather than calling the route', async () => { + await expect( + organizations.fetchList?.(orgArgs({ oauthCredential: undefined })) + ).rejects.toThrow(/Missing credential/) + expect(mockRequestJson).not.toHaveBeenCalled() + }) +}) + +describe('zoho_desk.departments selector', () => { + beforeEach(() => vi.clearAllMocks()) + + it('stays disabled until both the credential and the organization are set', () => { + expect(departments.enabled?.(deptArgs())).toBe(true) + expect(departments.enabled?.(deptArgs({ orgId: undefined }))).toBe(false) + expect(departments.enabled?.(deptArgs({ oauthCredential: undefined }))).toBe(false) + }) + + it('keys the query by credential and organization so switching portals refetches', () => { + expect(departments.getQueryKey(deptArgs())).toEqual([ + 'selectors', + 'zoho_desk.departments', + 'cred-1', + 'org-9', + ]) + expect(departments.getQueryKey(deptArgs({ orgId: undefined }))).toEqual([ + 'selectors', + 'zoho_desk.departments', + 'cred-1', + 'none', + ]) + }) + + it('forwards the organization id and maps departments to options', async () => { + mockRequestJson.mockResolvedValue({ + departments: [{ id: '1892000000082069', name: 'Zylker' }], + }) + + const options = await departments.fetchList?.(deptArgs()) + + expect(mockRequestJson).toHaveBeenCalledWith( + expect.objectContaining({ path: '/api/tools/zoho_desk/departments' }), + expect.objectContaining({ + body: { credential: 'cred-1', orgId: 'org-9', workflowId: 'wf-1' }, + }) + ) + expect(options).toEqual([{ id: '1892000000082069', label: 'Zylker' }]) + }) + + it('throws when the organization is missing rather than calling the route unscoped', async () => { + await expect(departments.fetchList?.(deptArgs({ orgId: undefined }))).rejects.toThrow( + /Missing organization ID/ + ) + expect(mockRequestJson).not.toHaveBeenCalled() + }) +}) + +describe('zoho_desk.agents selector', () => { + beforeEach(() => vi.clearAllMocks()) + + it('stays disabled until both the credential and the organization are set', () => { + expect(agents.enabled?.(agentArgs())).toBe(true) + expect(agents.enabled?.(agentArgs({ orgId: undefined }))).toBe(false) + expect(agents.enabled?.(agentArgs({ oauthCredential: undefined }))).toBe(false) + }) + + it('keys the query by credential and organization so switching portals refetches', () => { + expect(agents.getQueryKey(agentArgs())).toEqual([ + 'selectors', + 'zoho_desk.agents', + 'cred-1', + 'org-9', + ]) + expect(agents.getQueryKey(agentArgs({ orgId: undefined }))).toEqual([ + 'selectors', + 'zoho_desk.agents', + 'cred-1', + 'none', + ]) + }) + + it('forwards the organization id and maps agents to options', async () => { + mockRequestJson.mockResolvedValue({ + agents: [ + { id: '1892000000056007', name: 'zyl case' }, + { id: '1892000000042001', name: 'jade' }, + ], + }) + + const options = await agents.fetchList?.(agentArgs()) + + expect(mockRequestJson).toHaveBeenCalledWith( + expect.objectContaining({ path: '/api/tools/zoho_desk/agents' }), + expect.objectContaining({ + body: { credential: 'cred-1', orgId: 'org-9', workflowId: 'wf-1' }, + }) + ) + expect(options).toEqual([ + { id: '1892000000056007', label: 'zyl case' }, + { id: '1892000000042001', label: 'jade' }, + ]) + }) + + it('throws when the organization is missing rather than calling the route unscoped', async () => { + await expect(agents.fetchList?.(agentArgs({ orgId: undefined }))).rejects.toThrow( + /Missing organization ID/ + ) + expect(mockRequestJson).not.toHaveBeenCalled() + }) + + it('throws when the credential is missing rather than calling the route', async () => { + await expect(agents.fetchList?.(agentArgs({ oauthCredential: undefined }))).rejects.toThrow( + /Missing credential/ + ) + expect(mockRequestJson).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/hooks/selectors/providers/zoho-desk/selectors.ts b/apps/sim/hooks/selectors/providers/zoho-desk/selectors.ts new file mode 100644 index 00000000000..31fad78e433 --- /dev/null +++ b/apps/sim/hooks/selectors/providers/zoho-desk/selectors.ts @@ -0,0 +1,96 @@ +import { requestJson } from '@/lib/api/client/request' +import * as selectorContracts from '@/lib/api/contracts/selectors' +import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared' +import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types' + +export const zohoDeskSelectors = { + 'zoho_desk.organizations': { + key: 'zoho_desk.organizations', + contracts: [selectorContracts.zohoDeskOrganizationsSelectorContract], + staleTime: SELECTOR_STALE, + getQueryKey: ({ context }: SelectorQueryArgs) => [ + 'selectors', + 'zoho_desk.organizations', + context.oauthCredential ?? 'none', + ], + enabled: ({ context }) => Boolean(context.oauthCredential), + fetchList: async ({ context, signal }: SelectorQueryArgs) => { + const credentialId = ensureCredential(context, 'zoho_desk.organizations') + const data = await requestJson(selectorContracts.zohoDeskOrganizationsSelectorContract, { + body: { credential: credentialId, workflowId: context.workflowId }, + signal, + }) + return (data.organizations || []).map((organization) => ({ + id: organization.id, + label: organization.name, + })) + }, + }, + 'zoho_desk.departments': { + key: 'zoho_desk.departments', + contracts: [selectorContracts.zohoDeskDepartmentsSelectorContract], + staleTime: SELECTOR_STALE, + getQueryKey: ({ context }: SelectorQueryArgs) => [ + 'selectors', + 'zoho_desk.departments', + context.oauthCredential ?? 'none', + context.orgId ?? 'none', + ], + // Every Desk call but `/organizations` is scoped by the `orgId` header, so + // the organization must be chosen before departments can be listed. + enabled: ({ context }) => Boolean(context.oauthCredential && context.orgId), + fetchList: async ({ context, signal }: SelectorQueryArgs) => { + const credentialId = ensureCredential(context, 'zoho_desk.departments') + if (!context.orgId) { + throw new Error('Missing organization ID for zoho_desk.departments selector') + } + const data = await requestJson(selectorContracts.zohoDeskDepartmentsSelectorContract, { + body: { + credential: credentialId, + orgId: context.orgId, + workflowId: context.workflowId, + }, + signal, + }) + return (data.departments || []).map((department) => ({ + id: department.id, + label: department.name, + })) + }, + }, + 'zoho_desk.agents': { + key: 'zoho_desk.agents', + contracts: [selectorContracts.zohoDeskAgentsSelectorContract], + staleTime: SELECTOR_STALE, + getQueryKey: ({ context }: SelectorQueryArgs) => [ + 'selectors', + 'zoho_desk.agents', + context.oauthCredential ?? 'none', + context.orgId ?? 'none', + ], + // Same `orgId` header scoping as departments: the organization must be + // chosen before agents can be listed. + enabled: ({ context }) => Boolean(context.oauthCredential && context.orgId), + fetchList: async ({ context, signal }: SelectorQueryArgs) => { + const credentialId = ensureCredential(context, 'zoho_desk.agents') + if (!context.orgId) { + throw new Error('Missing organization ID for zoho_desk.agents selector') + } + const data = await requestJson(selectorContracts.zohoDeskAgentsSelectorContract, { + body: { + credential: credentialId, + orgId: context.orgId, + workflowId: context.workflowId, + }, + signal, + }) + return (data.agents || []).map((agent) => ({ + id: agent.id, + label: agent.name, + })) + }, + }, +} satisfies Record< + Extract, + SelectorDefinition +> diff --git a/apps/sim/hooks/selectors/registry.ts b/apps/sim/hooks/selectors/registry.ts index 45e29e029b2..912469f9c67 100644 --- a/apps/sim/hooks/selectors/registry.ts +++ b/apps/sim/hooks/selectors/registry.ts @@ -21,6 +21,7 @@ import { slackSelectors } from '@/hooks/selectors/providers/slack/selectors' import { trelloSelectors } from '@/hooks/selectors/providers/trello/selectors' import { wealthboxSelectors } from '@/hooks/selectors/providers/wealthbox/selectors' import { webflowSelectors } from '@/hooks/selectors/providers/webflow/selectors' +import { zohoDeskSelectors } from '@/hooks/selectors/providers/zoho-desk/selectors' import { zoomSelectors } from '@/hooks/selectors/providers/zoom/selectors' import type { SelectorDefinition, @@ -43,6 +44,7 @@ export const selectorRegistry = { ...pipedriveSelectors, ...sharepointSelectors, ...trelloSelectors, + ...zohoDeskSelectors, ...zoomSelectors, ...slackSelectors, ...wealthboxSelectors, diff --git a/apps/sim/hooks/selectors/types.ts b/apps/sim/hooks/selectors/types.ts index c8267d1369e..88ea585d797 100644 --- a/apps/sim/hooks/selectors/types.ts +++ b/apps/sim/hooks/selectors/types.ts @@ -26,6 +26,9 @@ export type SelectorKey = | 'pipedrive.pipelines' | 'sharepoint.lists' | 'trello.boards' + | 'zoho_desk.organizations' + | 'zoho_desk.departments' + | 'zoho_desk.agents' | 'zoom.meetings' | 'slack.channels' | 'slack.users' @@ -101,6 +104,8 @@ export interface SelectorContext { logGroupName?: string mcpServerId?: string tableId?: string + /** Zoho Desk organization (portal) id — the `orgId` header every Desk call but `/organizations` requires. */ + orgId?: string } export interface SelectorQueryArgs { diff --git a/apps/sim/lib/api/contracts/credentials.ts b/apps/sim/lib/api/contracts/credentials.ts index 99bfd56adfa..6a5c459e5fb 100644 --- a/apps/sim/lib/api/contracts/credentials.ts +++ b/apps/sim/lib/api/contracts/credentials.ts @@ -133,6 +133,8 @@ export const createCredentialBodySchema = z clientId: z.string().trim().min(1).max(512).optional(), clientSecret: z.string().trim().min(1).max(1024).optional(), orgId: z.string().trim().min(1).max(255).optional(), + /** Optional provider region selector (Zoho Desk data center). */ + dataCenter: z.string().trim().min(1).max(32).optional(), }) .superRefine((data, ctx) => { if (data.type === 'oauth') { @@ -207,6 +209,7 @@ export const updateCredentialByIdBodySchema = z clientId: z.string().trim().min(1).max(512).optional(), clientSecret: z.string().trim().min(1).max(1024).optional(), orgId: z.string().trim().min(1).max(255).optional(), + dataCenter: z.string().trim().min(1).max(32).optional(), }) .strict() .refine( @@ -220,7 +223,8 @@ export const updateCredentialByIdBodySchema = z data.domain !== undefined || data.clientId !== undefined || data.clientSecret !== undefined || - data.orgId !== undefined, + data.orgId !== undefined || + data.dataCenter !== undefined, { message: 'At least one field must be provided', path: ['displayName'], diff --git a/apps/sim/lib/api/contracts/oauth-connections.ts b/apps/sim/lib/api/contracts/oauth-connections.ts index a1de918b1df..103aab8144d 100644 --- a/apps/sim/lib/api/contracts/oauth-connections.ts +++ b/apps/sim/lib/api/contracts/oauth-connections.ts @@ -86,6 +86,8 @@ const oauthTokenResponseSchema = z.object({ accessToken: z.string(), idToken: z.string().optional(), instanceUrl: z.string().optional(), + /** Zoho Desk — the data-center-scoped Desk REST base for this credential. */ + apiDomain: z.string().optional(), cloudId: z.string().optional(), domain: z.string().optional(), authStyle: z.enum(['x-api-token']).optional(), diff --git a/apps/sim/lib/api/contracts/selectors/index.ts b/apps/sim/lib/api/contracts/selectors/index.ts index 3cfd1d15116..1b98ca9774c 100644 --- a/apps/sim/lib/api/contracts/selectors/index.ts +++ b/apps/sim/lib/api/contracts/selectors/index.ts @@ -107,6 +107,11 @@ import { webflowItemsSelectorContract, webflowSitesSelectorContract, } from '@/lib/api/contracts/selectors/webflow' +import { + zohoDeskAgentsSelectorContract, + zohoDeskDepartmentsSelectorContract, + zohoDeskOrganizationsSelectorContract, +} from '@/lib/api/contracts/selectors/zoho-desk' import { zoomMeetingsSelectorContract } from '@/lib/api/contracts/selectors/zoom' export * from '@/lib/api/contracts/selectors/airtable' @@ -133,6 +138,7 @@ export * from '@/lib/api/contracts/selectors/slack' export * from '@/lib/api/contracts/selectors/trello' export * from '@/lib/api/contracts/selectors/wealthbox' export * from '@/lib/api/contracts/selectors/webflow' +export * from '@/lib/api/contracts/selectors/zoho-desk' export * from '@/lib/api/contracts/selectors/zoom' export const selectorContractsByPath = { @@ -162,6 +168,9 @@ export const selectorContractsByPath = { '/api/tools/sharepoint/site': sharepointSiteSelectorContract, '/api/tools/sharepoint/sites': sharepointSitesSelectorContract, '/api/tools/trello/boards': trelloBoardsSelectorContract, + '/api/tools/zoho_desk/organizations': zohoDeskOrganizationsSelectorContract, + '/api/tools/zoho_desk/departments': zohoDeskDepartmentsSelectorContract, + '/api/tools/zoho_desk/agents': zohoDeskAgentsSelectorContract, '/api/tools/zoom/meetings': zoomMeetingsSelectorContract, '/api/tools/slack/channels': slackChannelsSelectorContract, '/api/tools/slack/users': slackUsersSelectorContract, diff --git a/apps/sim/lib/api/contracts/selectors/oauth.ts b/apps/sim/lib/api/contracts/selectors/oauth.ts index 2afd417f68d..24c04703835 100644 --- a/apps/sim/lib/api/contracts/selectors/oauth.ts +++ b/apps/sim/lib/api/contracts/selectors/oauth.ts @@ -10,6 +10,7 @@ const oauthTokenResponseSchema = z instanceUrl: z.string().optional(), cloudId: z.string().optional(), domain: z.string().optional(), + apiDomain: z.string().optional(), }) .passthrough() diff --git a/apps/sim/lib/api/contracts/selectors/zoho-desk.ts b/apps/sim/lib/api/contracts/selectors/zoho-desk.ts new file mode 100644 index 00000000000..8da5d624184 --- /dev/null +++ b/apps/sim/lib/api/contracts/selectors/zoho-desk.ts @@ -0,0 +1,42 @@ +import { z } from 'zod' +import { + credentialWorkflowBodySchema, + definePostSelector, + idNameSchema, +} from '@/lib/api/contracts/selectors/shared' +import type { ContractJsonResponse } from '@/lib/api/contracts/types' + +/** + * Zoho Desk scopes every call except `GET /organizations` to a single portal via + * the `orgId` header, so every downstream selector must carry the organization + * the user picked. + */ +const zohoDeskOrgIdSchema = z.string().min(1, 'orgId is required') + +export const zohoDeskOrganizationsSelectorContract = definePostSelector( + '/api/tools/zoho_desk/organizations', + credentialWorkflowBodySchema, + z.object({ organizations: z.array(idNameSchema) }) +) + +export const zohoDeskDepartmentsSelectorContract = definePostSelector( + '/api/tools/zoho_desk/departments', + credentialWorkflowBodySchema.extend({ orgId: zohoDeskOrgIdSchema }), + z.object({ departments: z.array(idNameSchema) }) +) + +export const zohoDeskAgentsSelectorContract = definePostSelector( + '/api/tools/zoho_desk/agents', + credentialWorkflowBodySchema.extend({ orgId: zohoDeskOrgIdSchema }), + z.object({ agents: z.array(idNameSchema) }) +) + +export type ZohoDeskOrganizationsSelectorResponse = ContractJsonResponse< + typeof zohoDeskOrganizationsSelectorContract +> +export type ZohoDeskDepartmentsSelectorResponse = ContractJsonResponse< + typeof zohoDeskDepartmentsSelectorContract +> +export type ZohoDeskAgentsSelectorResponse = ContractJsonResponse< + typeof zohoDeskAgentsSelectorContract +> diff --git a/apps/sim/lib/api/contracts/tools/zoho-desk.ts b/apps/sim/lib/api/contracts/tools/zoho-desk.ts new file mode 100644 index 00000000000..e05e9803efd --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/zoho-desk.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +import type { ContractBodyInput, ContractJsonResponse } from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' + +const zohoAccessTokenSchema = z.string().min(1, 'Access token is required') +const zohoApiDomainSchema = z.string().optional().nullable() +const zohoOrgIdSchema = z.string().min(1, 'Organization ID is required') + +export const zohoDeskGetAttachmentBodySchema = z.object({ + accessToken: zohoAccessTokenSchema, + apiDomain: zohoApiDomainSchema, + orgId: zohoOrgIdSchema, + // The documented download reference from an attachment object (thread/comment + // attachments expose `href`). Absolute Zoho URLs are used as-is; a relative + // path is resolved against the Desk API base. + href: z.string().min(1, 'Attachment href is required'), + fileName: z.string().optional().nullable(), +}) + +const zohoDeskFileSchema = z.object({ + data: z.string(), + mimeType: z.string(), + // FileToolProcessor (ToolFileData) reads the file name from `name`. + name: z.string(), +}) + +export const zohoDeskGetAttachmentResponseSchema = z.object({ + success: z.boolean(), + output: z.object({ file: zohoDeskFileSchema }).optional(), + error: z.string().optional(), +}) + +export const zohoDeskGetAttachmentContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/zoho_desk/attachment', + body: zohoDeskGetAttachmentBodySchema, + response: { mode: 'json', schema: zohoDeskGetAttachmentResponseSchema }, +}) + +export type ZohoDeskGetAttachmentBody = ContractBodyInput +export type ZohoDeskGetAttachmentResponse = ContractJsonResponse< + typeof zohoDeskGetAttachmentContract +> diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 48d9097e109..e7efb1a0e28 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -109,6 +109,7 @@ import { joinInstanceOrganization } from '@/lib/organizations/instance-org' import { captureServerEvent, getPostHogClient } from '@/lib/posthog/server' import { disableUserResources } from '@/lib/workflows/lifecycle' import { SSO_TRUSTED_PROVIDERS } from '@/ee/sso/constants' +import { deriveZohoDeskBaseFromApiDomain } from '@/tools/zoho_desk/host-allowlist' const logger = createLogger('Auth') @@ -2028,6 +2029,161 @@ export const auth = betterAuth({ }, }, + { + providerId: 'zoho-desk', + clientId: env.ZOHO_CLIENT_ID as string, + clientSecret: env.ZOHO_CLIENT_SECRET as string, + authorizationUrl: 'https://accounts.zoho.com/oauth/v2/auth', + tokenUrl: 'https://accounts.zoho.com/oauth/v2/token', + scopes: getCanonicalScopesForProvider('zoho-desk'), + responseType: 'code', + pkce: true, + accessType: 'offline', + prompt: 'consent', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/zoho-desk`, + // Zoho only issues a refresh token when access_type=offline AND + // prompt=consent are present on the authorize request, and it expects + // comma-separated scopes rather than the default space-delimited list. + authorizationUrlParams: { + access_type: 'offline', + prompt: 'consent', + scope: getCanonicalScopesForProvider('zoho-desk').join(','), + }, + getToken: async ({ code, redirectURI, codeVerifier }) => { + const tokenParams = new URLSearchParams({ + client_id: env.ZOHO_CLIENT_ID as string, + client_secret: env.ZOHO_CLIENT_SECRET as string, + code, + grant_type: 'authorization_code', + redirect_uri: redirectURI, + }) + // PKCE is enabled, so better-auth sent a code_challenge on the authorize + // request. The exchange MUST echo the matching code_verifier or Zoho + // rejects the request shape (invalid_request). Verified by isolating + // pkce:false (which connected) then restoring pkce:true + this verifier. + if (codeVerifier) tokenParams.set('code_verifier', codeVerifier) + + const response = await fetch('https://accounts.zoho.com/oauth/v2/token', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: tokenParams, + }) + const data = await readResponseJsonWithLimit>(response, { + maxBytes: 1024 * 1024, + label: 'Zoho Desk OAuth token response', + }) + + // Zoho signals OAuth failures in the JSON body, usually with HTTP 200, + // e.g. { error: 'invalid_code' } or { error: 'invalid_client', + // error_description: '...' }. The status-only guard therefore never + // fires, so surface the actual error/description instead of collapsing + // every failure into one opaque "no access token" string. + const errorObj = + data && typeof data === 'object' && !Array.isArray(data) + ? (data as { error?: unknown; error_description?: unknown }) + : {} + const zohoError = typeof errorObj.error === 'string' ? errorObj.error : undefined + const zohoErrorDescription = + typeof errorObj.error_description === 'string' + ? errorObj.error_description + : undefined + if ( + !response.ok || + !data || + typeof data !== 'object' || + Array.isArray(data) || + zohoError + ) { + logger.error('Zoho Desk OAuth token exchange failed', { + status: response.status, + zohoError: zohoError ?? null, + zohoErrorDescription: zohoErrorDescription ?? null, + }) + throw new Error( + `Zoho Desk OAuth token exchange failed (HTTP ${response.status}${ + zohoError ? `, ${zohoError}` : '' + }${zohoErrorDescription ? `: ${zohoErrorDescription}` : ''})` + ) + } + + const tokens = getOAuth2Tokens(data) + if (!tokens.accessToken) { + logger.error('Zoho Desk OAuth token response had no access token', { + status: response.status, + bodyKeys: Object.keys(data), + }) + throw new Error('Zoho Desk OAuth token response did not include an access token') + } + + // Persist the data-center-scoped Desk REST base derived from the + // token response api_domain so every API call targets the correct + // host instead of assuming desk.zoho.com. Stored inside the scope + // string (survives refreshes, which never rewrite scope) and read + // back in /api/auth/oauth/token as `apiDomain`. + const deskBase = deriveZohoDeskBaseFromApiDomain( + typeof data.api_domain === 'string' ? data.api_domain : undefined + ) + // Zoho's docs are inconsistent about whether the Desk token response + // carries `scope` (the Mail sample has it; the CRM/Creator samples do + // not). If it is absent, fall back to the scopes we requested and were + // granted by completing the flow - otherwise the stored scope list is + // just the domain marker, and the credential picker would show a + // permanent "needs update / reconnect" badge on every connection. + // Mirrors the existing Box fallback in this file. + const reportedScopes = + typeof data.scope === 'string' ? data.scope.split(/[\s,]+/).filter(Boolean) : [] + const grantedScopes = reportedScopes.length + ? reportedScopes + : getCanonicalScopesForProvider('zoho-desk') + tokens.scopes = [`__zoho_domain__:${deskBase}`, ...grantedScopes] + return tokens + }, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://accounts.zoho.com/oauth/user/info', { + headers: { Authorization: `Zoho-oauthtoken ${tokens.accessToken}` }, + }) + + if (!response.ok) { + await readResponseTextWithLimit(response, { + maxBytes: 1024 * 1024, + label: 'Zoho Desk profile error response', + }).catch(() => {}) + logger.error('Error fetching Zoho Desk user info:', { + status: response.status, + statusText: response.statusText, + }) + return null + } + + const profile = await readResponseJsonWithLimit<{ + ZUID?: number | string + Display_Name?: string + Email?: string + }>(response, { maxBytes: 1024 * 1024, label: 'Zoho Desk profile response' }) + + const zuid = profile.ZUID?.toString() + if (!zuid) { + logger.error('Invalid Zoho Desk profile response:', profile) + return null + } + + const now = new Date() + return { + id: `${zuid}-${generateId()}`, + name: profile.Display_Name || 'Zoho User', + email: profile.Email || `zoho-${zuid}@zoho.user`, + emailVerified: Boolean(profile.Email), + createdAt: now, + updatedAt: now, + } + } catch (error) { + logger.error('Error in Zoho Desk getUserInfo:', { error }) + return null + } + }, + }, + { providerId: 'x', clientId: env.X_CLIENT_ID as string, diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 9d16e13ab4f..939942670e4 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -411,6 +411,8 @@ export const env = createEnv({ HUBSPOT_CLIENT_SECRET: z.string().optional(), // HubSpot OAuth client secret SALESFORCE_CLIENT_ID: z.string().optional(), // Salesforce OAuth client ID SALESFORCE_CLIENT_SECRET: z.string().optional(), // Salesforce OAuth client secret + ZOHO_CLIENT_ID: z.string().optional(), // Zoho OAuth client ID (Zoho Desk) + ZOHO_CLIENT_SECRET: z.string().optional(), // Zoho OAuth client secret (Zoho Desk) WEALTHBOX_CLIENT_ID: z.string().optional(), // WealthBox OAuth client ID WEALTHBOX_CLIENT_SECRET: z.string().optional(), // WealthBox OAuth client secret PIPEDRIVE_CLIENT_ID: z.string().optional(), // Pipedrive OAuth client ID diff --git a/apps/sim/lib/credentials/client-credential-accounts/descriptors.ts b/apps/sim/lib/credentials/client-credential-accounts/descriptors.ts index fab342a0c38..f2439cabd38 100644 --- a/apps/sim/lib/credentials/client-credential-accounts/descriptors.ts +++ b/apps/sim/lib/credentials/client-credential-accounts/descriptors.ts @@ -15,7 +15,7 @@ export const CLIENT_CREDENTIAL_ACCOUNT_SECRET_TYPE = 'client_credential_account' as const /** Contract field ids a client-credential connect modal collects. */ -export type ClientCredentialAccountFieldId = 'clientId' | 'clientSecret' | 'orgId' +export type ClientCredentialAccountFieldId = 'clientId' | 'clientSecret' | 'orgId' | 'dataCenter' export interface ClientCredentialAccountField { id: ClientCredentialAccountFieldId @@ -23,6 +23,12 @@ export interface ClientCredentialAccountField { placeholder: string /** Rendered with SecretInput and never echoed back. */ secret: boolean + /** + * Field the connect modal may submit empty; excluded from + * {@link CLIENT_CREDENTIAL_ACCOUNT_REQUIRED_FIELDS} so create/reconnect + * validation never demands it. Omitted (default) means required. + */ + optional?: boolean /** Soft-format hint shown while the current value doesn't match `hintPattern`. */ hintPattern?: RegExp hintMessage?: string @@ -54,11 +60,13 @@ export interface ClientCredentialAccountDescriptor { export const ZOOM_SERVICE_ACCOUNT_PROVIDER_ID = 'zoom-service-account' as const export const BOX_SERVICE_ACCOUNT_PROVIDER_ID = 'box-service-account' as const export const SALESFORCE_SERVICE_ACCOUNT_PROVIDER_ID = 'salesforce-service-account' as const +export const ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID = 'zoho-desk-service-account' as const export type ClientCredentialAccountProviderId = | typeof ZOOM_SERVICE_ACCOUNT_PROVIDER_ID | typeof BOX_SERVICE_ACCOUNT_PROVIDER_ID | typeof SALESFORCE_SERVICE_ACCOUNT_PROVIDER_ID + | typeof ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID /** * Allowed My Domain host shapes: one org label (optionally with a @@ -85,6 +93,95 @@ export function normalizeSalesforceMyDomainHost(rawHost: string): string { .toLowerCase() } +/** + * Zoho's `soid` token parameter is documented only as the syntax + * `{servicename}.{zsoid}` with a single Zoho CRM example + * (`ZohoCRM.600*****434`). Two things are NOT confirmed by Zoho's own docs: + * + * 1. that the Desk service name is literally `ZohoDesk` (inferred from the + * documented syntax and corroborated only by community posts), and + * 2. that `zsoid` is the same value as the Desk `orgId` sent in the `orgId` + * request header, rather than a distinct Zoho ServiceOrg id. + * + * Both need live verification against a real Zoho Desk org before this flow is + * relied on. The normalization is therefore deliberately permissive: a value + * that already carries a `{servicename}.` prefix (any dot) is passed through + * untouched, so an operator who learns the correct prefix or id can paste the + * full `soid` and bypass the inference entirely. A bare id is prefixed with + * `ZohoDesk.`. + * + * Shared by the connect modal's format hint and the server-side minter so both + * judge the same normalized value. + */ +export function normalizeZohoDeskSoid(rawOrgId: string): string { + const trimmed = rawOrgId.trim() + if (!trimmed || trimmed.includes('.')) return trimmed + return `ZohoDesk.${trimmed}` +} + +/** A normalized `soid`: a service-name prefix plus a numeric Zoho org id. */ +export const ZOHO_DESK_SOID_REGEX = /^[A-Za-z]+\.\d+$/ + +/** + * Zoho data centers the Self Client (client-credentials) flow supports. Each + * entry pairs the accounts server that mints the token with the Desk REST host + * in the same region, so the minter never has to guess either one. + * + * Only regions where BOTH hosts are confirmed are listed. CA, SA, JP, CN, and + * UK are deliberately absent: Zoho's accounts documentation and Zoho's own Desk + * SDK disagree on Canada (`accounts.zohocloud.ca` vs `accounts.zoho.ca`), and + * the Desk hosts for the others are not confirmed. More regions can be added + * once both the accounts server and the Desk host are confirmed for them. + * + * This applies to the service account only — the interactive OAuth flow's + * authorize/token URLs are static per provider and remain US-only. + */ +export const ZOHO_DESK_DATA_CENTERS = { + us: { accountsBase: 'https://accounts.zoho.com', deskBase: 'https://desk.zoho.com' }, + eu: { accountsBase: 'https://accounts.zoho.eu', deskBase: 'https://desk.zoho.eu' }, + in: { accountsBase: 'https://accounts.zoho.in', deskBase: 'https://desk.zoho.in' }, + au: { accountsBase: 'https://accounts.zoho.com.au', deskBase: 'https://desk.zoho.com.au' }, +} as const satisfies Record + +export type ZohoDeskDataCenterId = keyof typeof ZOHO_DESK_DATA_CENTERS + +/** Region used when the admin leaves the field blank, so existing credentials are unaffected. */ +export const DEFAULT_ZOHO_DESK_DATA_CENTER: ZohoDeskDataCenterId = 'us' + +export const ZOHO_DESK_DATA_CENTER_IDS = Object.keys( + ZOHO_DESK_DATA_CENTERS +) as ZohoDeskDataCenterId[] + +/** Accepts exactly the supported region codes, case-insensitively after normalization. */ +export const ZOHO_DESK_DATA_CENTER_REGEX = new RegExp(`^(${ZOHO_DESK_DATA_CENTER_IDS.join('|')})$`) + +/** + * Normalizes a pasted data-center value to its lowercase region code. Shared by + * the connect modal's format hint and the server-side minter so both judge the + * same normalized value. + */ +export function normalizeZohoDeskDataCenter(rawDataCenter: string): string { + return rawDataCenter.trim().toLowerCase() +} + +/** + * Resolves a stored data-center value to its accounts/Desk host pair. A blank, + * absent, or unrecognized value falls back to {@link DEFAULT_ZOHO_DESK_DATA_CENTER} + * so credentials created before this field existed keep minting against the US + * accounts server exactly as they did. + */ +export function resolveZohoDeskDataCenter(rawDataCenter?: string): { + id: ZohoDeskDataCenterId + accountsBase: string + deskBase: string +} { + const normalized = normalizeZohoDeskDataCenter(rawDataCenter ?? '') + const id: ZohoDeskDataCenterId = Object.hasOwn(ZOHO_DESK_DATA_CENTERS, normalized) + ? (normalized as ZohoDeskDataCenterId) + : DEFAULT_ZOHO_DESK_DATA_CENTER + return { id, ...ZOHO_DESK_DATA_CENTERS[id] } +} + export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record< ClientCredentialAccountProviderId, ClientCredentialAccountDescriptor @@ -179,12 +276,55 @@ export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record< helpText: 'The Connected App must have "Enable Client Credentials Flow" checked with a "Run As" integration user set under Edit Policies — every call executes with that user\'s permissions, and deactivating or freezing the user stops all runs.', }, + [ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID]: { + providerId: ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID, + serviceLabel: 'Zoho Desk', + connectNoun: 'Self Client', + fields: [ + { + id: 'clientId', + label: 'Client ID', + placeholder: "Client ID from the Self Client's Client Secret tab", + secret: false, + }, + { + id: 'clientSecret', + label: 'Client secret', + placeholder: 'Paste the client secret', + secret: true, + }, + { + id: 'orgId', + label: 'Organization ID', + placeholder: '600123456', + secret: false, + hintPattern: ZOHO_DESK_SOID_REGEX, + hintNormalize: normalizeZohoDeskSoid, + hintMessage: + 'Paste the numeric Zoho Desk organization ID from Setup → Developer Space → API, or the full ZohoDesk. value.', + }, + { + id: 'dataCenter', + label: 'Data center', + placeholder: 'us', + secret: false, + optional: true, + hintPattern: ZOHO_DESK_DATA_CENTER_REGEX, + hintNormalize: normalizeZohoDeskDataCenter, + hintMessage: `Enter one of ${ZOHO_DESK_DATA_CENTER_IDS.join(', ')}. Leave blank for us (accounts.zoho.com).`, + }, + ], + docsUrl: 'https://docs.sim.ai/integrations/zoho-desk-service-account', + helpText: + 'Create the Self Client in the Zoho API Console, add the Zoho Desk scopes, and use the organization ID from Setup → Developer Space → API. Self Clients work with the US, EU, IN, and AU data centers — leave the data center blank for US. Zoho Desk triggers still require an OAuth connection, which is US-only.', + }, } /** * Required contract fields per client-credential provider, consumed by the * `createCredentialBodySchema` superRefine so validation errors name the exact - * missing field. Derived from each descriptor's field list. + * missing field. Derived from each descriptor's field list, minus the fields + * marked `optional`. */ export const CLIENT_CREDENTIAL_ACCOUNT_REQUIRED_FIELDS: Record< string, @@ -192,7 +332,7 @@ export const CLIENT_CREDENTIAL_ACCOUNT_REQUIRED_FIELDS: Record< > = Object.fromEntries( Object.values(CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS).map((descriptor) => [ descriptor.providerId, - descriptor.fields.map((field) => field.id), + descriptor.fields.filter((field) => !field.optional).map((field) => field.id), ]) ) diff --git a/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.test.ts b/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.test.ts new file mode 100644 index 00000000000..839e452202a --- /dev/null +++ b/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.test.ts @@ -0,0 +1,420 @@ +/** + * @vitest-environment node + */ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockGetCanonicalScopesForProvider, mockLoggerWarn } = vi.hoisted(() => ({ + mockGetCanonicalScopesForProvider: vi.fn(), + mockLoggerWarn: vi.fn(), +})) + +vi.mock('@/lib/oauth/utils', () => ({ + getCanonicalScopesForProvider: mockGetCanonicalScopesForProvider, +})) + +/** + * Overrides the global `@sim/logger` mock (which hands out a fresh logger per + * `createLogger` call) with one whose `warn` is a shared spy, so the + * api_domain/region mismatch warning can be asserted. + */ +vi.mock('@sim/logger', () => { + const createLogger = () => ({ + debug: vi.fn(), + info: vi.fn(), + warn: mockLoggerWarn, + error: vi.fn(), + trace: vi.fn(), + fatal: vi.fn(), + child: vi.fn(() => createLogger()), + withMetadata: vi.fn(() => createLogger()), + }) + return { + createLogger: vi.fn(createLogger), + logger: createLogger(), + runWithRequestContext: vi.fn((_ctx: unknown, fn: () => T): T => fn()), + getRequestContext: vi.fn(() => undefined), + } +}) + +import { mintZohoDeskServiceAccountToken } from '@/lib/credentials/client-credential-accounts/minters/zoho-desk' + +const TOKEN_URL = 'https://accounts.zoho.com/oauth/v2/token' + +/** + * The four supported data centers, each with the accounts server the mint must + * POST to and the Desk REST base the result must carry. + */ +const DATA_CENTERS = [ + { id: 'us', tokenUrl: TOKEN_URL, deskBase: 'https://desk.zoho.com' }, + { + id: 'eu', + tokenUrl: 'https://accounts.zoho.eu/oauth/v2/token', + deskBase: 'https://desk.zoho.eu', + }, + { + id: 'in', + tokenUrl: 'https://accounts.zoho.in/oauth/v2/token', + deskBase: 'https://desk.zoho.in', + }, + { + id: 'au', + tokenUrl: 'https://accounts.zoho.com.au/oauth/v2/token', + deskBase: 'https://desk.zoho.com.au', + }, +] as const + +const SCOPES = ['Desk.tickets.READ', 'Desk.contacts.READ', 'aaaserver.profile.READ'] + +const FIELDS = { clientId: 'zoho-cid', clientSecret: 'zoho-secret', orgId: '600123456' } + +function jsonResponse(status: number, body: unknown): Response { + return { + ok: status >= 200 && status < 300, + status, + statusText: '', + json: async () => body, + text: async () => JSON.stringify(body), + } as unknown as Response +} + +function htmlResponse(status: number, body: string): Response { + return { + ok: status >= 200 && status < 300, + status, + statusText: '', + json: async () => { + throw new SyntaxError('Unexpected token < in JSON') + }, + text: async () => body, + } as unknown as Response +} + +const mockFetch = vi.fn() + +function mintBody(expectedUrl: string = TOKEN_URL): URLSearchParams { + const [url, init] = mockFetch.mock.calls[0] + expect(url).toBe(expectedUrl) + expect(init.method).toBe('POST') + expect(init.headers['Content-Type']).toBe('application/x-www-form-urlencoded') + return new URLSearchParams(init.body as string) +} + +describe('mintZohoDeskServiceAccountToken', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + mockGetCanonicalScopesForProvider.mockReturnValue(SCOPES) + }) + + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('returns the minted token, derived Desk base, scopes, and identity on success', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse(200, { + access_token: 'zoho-access', + api_domain: 'https://www.zohoapis.com', + token_type: 'Bearer', + expires_in: 3600, + scope: 'Desk.tickets.READ,Desk.contacts.READ', + }) + ) + + const result = await mintZohoDeskServiceAccountToken(FIELDS) + + expect(result).toEqual({ + accessToken: 'zoho-access', + expiresInSeconds: 3600, + apiDomain: 'https://desk.zoho.com', + grantedScopes: ['Desk.tickets.READ', 'Desk.contacts.READ'], + identity: { + displayName: 'Zoho Desk org 600123456', + auditMetadata: { + zohoDeskSoid: 'ZohoDesk.600123456', + zohoDeskClientId: 'zoho-cid', + }, + storedMetadata: { + soid: 'ZohoDesk.600123456', + apiDomain: 'https://desk.zoho.com', + dataCenter: 'us', + grantedScopes: 'Desk.tickets.READ Desk.contacts.READ', + }, + }, + }) + expect(mockFetch).toHaveBeenCalledTimes(1) + }) + + it('sends client_credentials with a COMMA-separated scope list and a ZohoDesk-prefixed soid', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + await mintZohoDeskServiceAccountToken(FIELDS) + + const body = mintBody() + expect(body.get('grant_type')).toBe('client_credentials') + expect(body.get('client_id')).toBe('zoho-cid') + expect(body.get('client_secret')).toBe('zoho-secret') + // Comma-separated, and Desk-only: `aaaserver.profile.READ` belongs to the + // interactive OAuth flow's getUserInfo call, which this grant never makes. + expect(body.get('scope')).toBe('Desk.tickets.READ,Desk.contacts.READ') + expect(body.get('scope')).not.toContain(' ') + expect(body.get('scope')).not.toContain('aaaserver') + expect(body.get('soid')).toBe('ZohoDesk.600123456') + expect(mockGetCanonicalScopesForProvider).toHaveBeenCalledWith('zoho-desk') + }) + + it('passes an already-prefixed soid through unchanged', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + await mintZohoDeskServiceAccountToken({ ...FIELDS, orgId: ' ZohoCRM.600123456 ' }) + + expect(mintBody().get('soid')).toBe('ZohoCRM.600123456') + }) + + it('derives the data-center Desk base from a non-US api_domain', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse(200, { access_token: 'zoho-access', api_domain: 'https://www.zohoapis.eu' }) + ) + + const result = await mintZohoDeskServiceAccountToken(FIELDS, { skipIdentity: true }) + + expect(result).toEqual({ + accessToken: 'zoho-access', + expiresInSeconds: 3600, + apiDomain: 'https://desk.zoho.eu', + grantedScopes: undefined, + }) + }) + + it('falls back to the US Desk base when api_domain is absent or not a Zoho host', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse(200, { access_token: 'a', api_domain: 'https://zoho.attacker.com' }) + ) + + const result = await mintZohoDeskServiceAccountToken(FIELDS, { skipIdentity: true }) + + expect(result.apiDomain).toBe('https://desk.zoho.com') + }) + + describe.each(DATA_CENTERS)('data center $id', ({ id, tokenUrl, deskBase }) => { + it('posts to the region accounts server and returns the region Desk base', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + const result = await mintZohoDeskServiceAccountToken( + { ...FIELDS, dataCenter: id }, + { skipIdentity: true } + ) + + expect(mintBody(tokenUrl).get('soid')).toBe('ZohoDesk.600123456') + expect(result.apiDomain).toBe(deskBase) + expect(mockLoggerWarn).not.toHaveBeenCalled() + }) + + it('accepts the region code with surrounding whitespace and mixed case', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + const result = await mintZohoDeskServiceAccountToken( + { ...FIELDS, dataCenter: ` ${id.toUpperCase()} ` }, + { skipIdentity: true } + ) + + mintBody(tokenUrl) + expect(result.apiDomain).toBe(deskBase) + }) + }) + + it.each([undefined, '', ' '])( + 'defaults to the US data center when dataCenter is %j', + async (dataCenter) => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + const result = await mintZohoDeskServiceAccountToken( + { ...FIELDS, dataCenter }, + { skipIdentity: true } + ) + + mintBody(TOKEN_URL) + expect(result.apiDomain).toBe('https://desk.zoho.com') + } + ) + + // A typed-but-unrecognized region must NOT quietly resolve to US and then fail + // against Zoho as an opaque invalid_client — the operator would have no way to + // tell a wrong region from wrong credentials. Blank still means US (see above). + it('rejects an unrecognized data center instead of silently using US', async () => { + await expect( + mintZohoDeskServiceAccountToken({ ...FIELDS, dataCenter: 'jp' }) + ).rejects.toMatchObject({ + code: 'invalid_credentials', + status: 400, + logDetail: expect.objectContaining({ step: 'data_center_validation', dataCenter: 'jp' }), + }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('records the resolved data center in the stored metadata', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { access_token: 'zoho-access' })) + + const result = await mintZohoDeskServiceAccountToken({ ...FIELDS, dataCenter: 'eu' }) + + expect(result.identity?.storedMetadata).toMatchObject({ + dataCenter: 'eu', + apiDomain: 'https://desk.zoho.eu', + }) + }) + + it('prefers api_domain over the selected region and warns when the two disagree', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse(200, { access_token: 'zoho-access', api_domain: 'https://www.zohoapis.in' }) + ) + + const result = await mintZohoDeskServiceAccountToken( + { ...FIELDS, dataCenter: 'eu' }, + { skipIdentity: true } + ) + + mintBody('https://accounts.zoho.eu/oauth/v2/token') + expect(result.apiDomain).toBe('https://desk.zoho.in') + expect(mockLoggerWarn).toHaveBeenCalledWith( + 'Zoho api_domain disagrees with the selected data center', + expect.objectContaining({ + selectedDataCenter: 'eu', + selectedDeskBase: 'https://desk.zoho.eu', + reportedDeskBase: 'https://desk.zoho.in', + }) + ) + }) + + it('keeps the selected region when api_domain is not an allowlisted Zoho host', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse(200, { access_token: 'zoho-access', api_domain: 'https://zoho.attacker.com' }) + ) + + const result = await mintZohoDeskServiceAccountToken( + { ...FIELDS, dataCenter: 'eu' }, + { skipIdentity: true } + ) + + expect(result.apiDomain).toBe('https://desk.zoho.eu') + expect(mockLoggerWarn).not.toHaveBeenCalled() + }) + + it('names the data center in the failure detail so a wrong region is diagnosable', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { error: 'invalid_client' })) + + await expect( + mintZohoDeskServiceAccountToken({ ...FIELDS, dataCenter: 'au' }) + ).rejects.toMatchObject({ + code: 'invalid_credentials', + logDetail: expect.objectContaining({ dataCenter: 'au' }), + }) + }) + + it('rejects a non-numeric organization ID before any network call', async () => { + await expect( + mintZohoDeskServiceAccountToken({ ...FIELDS, orgId: 'my-org' }) + ).rejects.toMatchObject({ + name: 'TokenServiceAccountValidationError', + code: 'invalid_credentials', + status: 400, + logDetail: expect.objectContaining({ step: 'soid_validation' }), + }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('throws invalid_credentials on an HTTP 200 body carrying an error field', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { error: 'invalid_client' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + name: 'TokenServiceAccountValidationError', + code: 'invalid_credentials', + status: 200, + logDetail: expect.objectContaining({ + zohoError: 'invalid_client', + soid: 'ZohoDesk.600123456', + // Matched loosely: the hint's exact wording is operator-facing copy, but + // it must keep naming the data center, since a wrong region is a leading + // cause of invalid_client on reconnect. + hint: expect.stringContaining('data center'), + }), + }) + }) + + it('names the soid in the failure detail when Zoho cannot resolve the org', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { error: 'missing_org_info' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'invalid_credentials', + logDetail: expect.objectContaining({ + soid: 'ZohoDesk.600123456', + hint: expect.stringContaining('Organization ID'), + }), + }) + }) + + it('throws invalid_credentials on a 400 with an error body', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(400, { error: 'invalid_scope' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'invalid_credentials', + status: 400, + logDetail: expect.objectContaining({ + hint: 'the Self Client was not granted the Zoho Desk scopes Sim requests', + }), + }) + }) + + it('throws invalid_credentials on a 401', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(401, { error: 'unauthorized' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'invalid_credentials', + status: 401, + }) + }) + + it('throws provider_unavailable (not invalid_credentials) on a 429 rate limit', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(429, { error: 'too_many_requests' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'provider_unavailable', + status: 429, + }) + }) + + it('throws provider_unavailable on a 503', async () => { + mockFetch.mockResolvedValueOnce(htmlResponse(503, 'unavailable')) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'provider_unavailable', + status: 503, + }) + }) + + it('throws provider_unavailable on a 200 with a non-JSON body', async () => { + mockFetch.mockResolvedValueOnce(htmlResponse(200, 'proxy page')) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'provider_unavailable', + status: 502, + }) + }) + + it('throws provider_unavailable when the success body is missing access_token', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse(200, { token_type: 'Bearer' })) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'provider_unavailable', + status: 502, + }) + }) + + it('throws provider_unavailable on a network error', async () => { + mockFetch.mockRejectedValueOnce(new TypeError('fetch failed')) + + await expect(mintZohoDeskServiceAccountToken(FIELDS)).rejects.toMatchObject({ + code: 'provider_unavailable', + status: 502, + }) + }) +}) diff --git a/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.ts b/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.ts new file mode 100644 index 00000000000..35f3f6f7963 --- /dev/null +++ b/apps/sim/lib/credentials/client-credential-accounts/minters/zoho-desk.ts @@ -0,0 +1,274 @@ +import { createLogger } from '@sim/logger' +import { + normalizeZohoDeskDataCenter, + normalizeZohoDeskSoid, + resolveZohoDeskDataCenter, + ZOHO_DESK_DATA_CENTER_IDS, + ZOHO_DESK_DATA_CENTER_REGEX, + ZOHO_DESK_SOID_REGEX, +} from '@/lib/credentials/client-credential-accounts/descriptors' +import type { + ClientCredentialAccountFields, + ClientCredentialAccountMintOptions, + ClientCredentialAccountMintResult, +} from '@/lib/credentials/client-credential-accounts/server' +import { + fetchProvider, + isTransientProviderStatus, + parseProviderJson, + readProviderErrorSnippet, + TokenServiceAccountValidationError, +} from '@/lib/credentials/token-service-accounts/errors' +import { getCanonicalScopesForProvider } from '@/lib/oauth/utils' +import { tryDeriveZohoDeskBaseFromApiDomain } from '@/tools/zoho_desk/host-allowlist' + +const logger = createLogger('ZohoDeskServiceAccountMinter') + +const STEP = 'zoho_desk_token_mint' + +/** Fallback token lifetime; Zoho documents one hour for this grant. */ +const ZOHO_DEFAULT_TOKEN_TTL_SECONDS = 3600 + +interface ZohoTokenResponse { + access_token?: string + api_domain?: string + token_type?: string + expires_in?: number + scope?: string + error?: string + error_description?: string +} + +/** + * Maps a Zoho token-endpoint `error` code to an operator-facing hint for server + * logs. Every value here means "fix the pasted credentials or the Self Client + * config", so all of them classify as `invalid_credentials`. + */ +function zohoErrorHint(error: string): string | undefined { + const normalized = error.toLowerCase() + if (normalized === 'invalid_client') { + // Also the symptom of a wrong data center: a Self Client only exists on its + // own region's accounts server, so an EU client presented to accounts.zoho.com + // reads as an unknown client. Reconnect re-mints from the submitted fields, so + // an admin who re-enters the secrets but leaves the region blank lands here. + return 'invalid client_id or client_secret, the client is not a Self Client, or the wrong data center was selected for it' + } + if (normalized === 'invalid_scope' || normalized === 'invalid_scopes') { + return 'the Self Client was not granted the Zoho Desk scopes Sim requests' + } + if (normalized === 'missing_org_info') { + return 'Zoho could not resolve the organization from soid — check the Organization ID (Setup > Developer Space > API)' + } + if (normalized === 'invalid_soid' || normalized === 'invalid_org') { + return 'the soid did not match a Zoho Desk organization — check the Organization ID, and try pasting the full ZohoDesk. value' + } + if (normalized === 'invalid_grant' || normalized === 'unsupported_grant_type') { + return 'the client does not support the client_credentials grant — create a Self Client in the Zoho API Console' + } + return undefined +} + +/** + * Extracts the `error` field from a Zoho token-endpoint body. Returns + * `undefined` for a body that is not JSON or carries no `error`. + */ +function zohoBodyError(body: string): string | undefined { + try { + const parsed = JSON.parse(body) as { error?: unknown } + return typeof parsed.error === 'string' && parsed.error ? parsed.error : undefined + } catch { + return undefined + } +} + +/** + * Builds the `invalid_credentials` failure for a rejected mint, naming the + * `soid` and the data center that were used so a wrong organization ID or a + * wrong region is diagnosable from the server log without echoing the client + * secret. + */ +function invalidCredentials( + status: number, + soid: string, + dataCenterId: string, + body: string, + error?: string +): TokenServiceAccountValidationError { + const hint = error ? zohoErrorHint(error) : undefined + return new TokenServiceAccountValidationError('invalid_credentials', status, { + step: STEP, + soid, + dataCenter: dataCenterId, + body, + ...(error ? { zohoError: error } : {}), + ...(hint ? { hint } : {}), + }) +} + +/** + * Mints a Zoho Desk access token via the Self Client `client_credentials` + * grant: POST `/oauth/v2/token` with `client_id`, + * `client_secret`, `scope`, and `soid` in the form body. Tokens live one hour + * and there is no refresh token — re-mint instead of refreshing (same shape as + * Zoom Server-to-Server). + * + * Zoho's accounts server is per data center. Unlike the interactive OAuth flow + * (whose authorize/token URLs are static per provider and therefore US-only), + * this path owns its token URL, so `fields.dataCenter` selects the region's + * accounts server and the matching Desk REST host deterministically. A blank or + * unrecognized value resolves to US, so credentials created before the field + * existed behave exactly as before. + * + * Two Zoho-specific behaviors drive the error handling: + * + * - Zoho reports OAuth failures in the JSON body, frequently with HTTP 200 + * (e.g. `{"error":"invalid_client"}`), so a status-only check would accept a + * failed mint. The success body is inspected for an `error` field before the + * token is read — mirroring the OAuth token exchange in `lib/auth/auth.ts`. + * - `scope` must be COMMA-separated for Zoho, not space-separated. The list is + * sourced from the `zoho-desk` OAuth service so the Self Client and the OAuth + * flow can never request different scopes. + * + * 4xx maps to `invalid_credentials` except transient 429/408 throttling, which + * maps to `provider_unavailable` alongside 5xx and network failures — provider + * throttling is never blamed on the credentials. + */ +export async function mintZohoDeskServiceAccountToken( + fields: ClientCredentialAccountFields, + options?: ClientCredentialAccountMintOptions +): Promise { + const soid = normalizeZohoDeskSoid(fields.orgId) + if (!ZOHO_DESK_SOID_REGEX.test(soid)) { + throw new TokenServiceAccountValidationError('invalid_credentials', 400, { + step: 'soid_validation', + soid, + reason: + 'organization ID is not a numeric Zoho Desk org id (or a full ZohoDesk. value)', + }) + } + + // A blank data center legitimately means "US" (the pre-field default), but a + // value that was typed and not recognized must not silently resolve to US and + // then fail against Zoho as an opaque `invalid_client`. Reject it here, next to + // the soid check, so the connect modal names the real problem. This runs on + // every execution-time re-mint too, where there is no UI to show a format hint. + const rawDataCenter = normalizeZohoDeskDataCenter(fields.dataCenter ?? '') + if (rawDataCenter && !ZOHO_DESK_DATA_CENTER_REGEX.test(rawDataCenter)) { + throw new TokenServiceAccountValidationError('invalid_credentials', 400, { + step: 'data_center_validation', + dataCenter: rawDataCenter, + reason: `unrecognized data center; expected one of ${ZOHO_DESK_DATA_CENTER_IDS.join(', ')} (or blank for us)`, + }) + } + + const dataCenter = resolveZohoDeskDataCenter(rawDataCenter) + + // Zoho requires a comma-separated scope list on this endpoint; a + // space-separated list is rejected as an invalid scope. + // Only the Desk.* scopes. `aaaserver.profile.READ` exists for the interactive + // OAuth flow's getUserInfo call; this grant never hits the Accounts profile + // endpoint (identity is synthesized from orgId, and skipIdentity bypasses it + // entirely at execution time). Sending an Accounts-server scope on the Desk + // soid grant risks an opaque invalid_scope rejection for no benefit. + const scope = getCanonicalScopesForProvider('zoho-desk') + .filter((s) => s.startsWith('Desk.')) + .join(',') + + const res = await fetchProvider( + `${dataCenter.accountsBase}/oauth/v2/token`, + { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: fields.clientId, + client_secret: fields.clientSecret, + scope, + soid, + }).toString(), + }, + STEP + ) + + if (!res.ok) { + const body = await readProviderErrorSnippet(res) + if (res.status >= 400 && res.status < 500 && !isTransientProviderStatus(res.status)) { + throw invalidCredentials(res.status, soid, dataCenter.id, body, zohoBodyError(body)) + } + throw new TokenServiceAccountValidationError('provider_unavailable', res.status, { + step: STEP, + soid, + dataCenter: dataCenter.id, + body, + }) + } + + const payload = await parseProviderJson(res, STEP) + + // Zoho signals OAuth failures in the body, usually with HTTP 200 — classify + // on the body before trusting the status. + if (typeof payload.error === 'string' && payload.error) { + throw invalidCredentials( + res.status, + soid, + dataCenter.id, + JSON.stringify(payload), + payload.error + ) + } + + if (typeof payload.access_token !== 'string' || !payload.access_token) { + throw new TokenServiceAccountValidationError('provider_unavailable', 502, { + step: STEP, + soid, + dataCenter: dataCenter.id, + reason: 'token response missing access_token', + }) + } + + // The Desk REST host for the token's data center, allowlist-anchored. Tools + // receive this as `apiDomain` so calls never assume desk.zoho.com. The + // selected region's Desk host is the deterministic answer; Zoho documents + // `api_domain` for CRM but not for Desk, so it is only an override. When it + // IS present and disagrees, it wins — it is authoritative about where the + // token actually works — but the mismatch is logged so a wrong region + // selection is diagnosable. An `api_domain` that is absent or fails the Zoho + // apex allowlist yields `undefined` here and never overrides the region. + const reportedDeskBase = tryDeriveZohoDeskBaseFromApiDomain(payload.api_domain) + const apiDomain = reportedDeskBase ?? dataCenter.deskBase + if (reportedDeskBase && reportedDeskBase !== dataCenter.deskBase) { + logger.warn('Zoho api_domain disagrees with the selected data center', { + soid, + selectedDataCenter: dataCenter.id, + selectedDeskBase: dataCenter.deskBase, + reportedDeskBase, + }) + } + const expiresInSeconds = + typeof payload.expires_in === 'number' && payload.expires_in > 0 + ? payload.expires_in + : ZOHO_DEFAULT_TOKEN_TTL_SECONDS + const grantedScopes = + typeof payload.scope === 'string' ? payload.scope.split(/[\s,]+/).filter(Boolean) : undefined + + if (options?.skipIdentity) { + return { accessToken: payload.access_token, expiresInSeconds, apiDomain, grantedScopes } + } + + const storedMetadata: Record = { soid, apiDomain, dataCenter: dataCenter.id } + if (grantedScopes?.length) { + storedMetadata.grantedScopes = grantedScopes.join(' ') + } + + return { + accessToken: payload.access_token, + expiresInSeconds, + apiDomain, + grantedScopes, + identity: { + displayName: `Zoho Desk org ${fields.orgId.trim()}`, + auditMetadata: { zohoDeskSoid: soid, zohoDeskClientId: fields.clientId }, + storedMetadata, + }, + } +} diff --git a/apps/sim/lib/credentials/client-credential-accounts/server.ts b/apps/sim/lib/credentials/client-credential-accounts/server.ts index 1e92d09d5b1..44b216f8406 100644 --- a/apps/sim/lib/credentials/client-credential-accounts/server.ts +++ b/apps/sim/lib/credentials/client-credential-accounts/server.ts @@ -4,18 +4,29 @@ import { type ClientCredentialAccountProviderId, isClientCredentialAccountProviderId, SALESFORCE_SERVICE_ACCOUNT_PROVIDER_ID, + ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID, ZOOM_SERVICE_ACCOUNT_PROVIDER_ID, } from '@/lib/credentials/client-credential-accounts/descriptors' import { mintBoxServiceAccountToken } from '@/lib/credentials/client-credential-accounts/minters/box' import { mintSalesforceServiceAccountToken } from '@/lib/credentials/client-credential-accounts/minters/salesforce' +import { mintZohoDeskServiceAccountToken } from '@/lib/credentials/client-credential-accounts/minters/zoho-desk' import { mintZoomServiceAccountToken } from '@/lib/credentials/client-credential-accounts/minters/zoom' /** Raw fields a client-credential minter receives (already trimmed). */ export interface ClientCredentialAccountFields { clientId: string clientSecret: string - /** Provider-specific org identifier (Zoom Account ID, Box Enterprise ID, Salesforce My Domain host). */ + /** + * Provider-specific org identifier (Zoom Account ID, Box Enterprise ID, + * Salesforce My Domain host, Zoho Desk organization ID). + */ orgId: string + /** + * Optional provider region selector. Only Zoho Desk uses it (the Self Client + * mints against a per-data-center accounts server); every other provider + * ignores it, and a blank value keeps the provider's default region. + */ + dataCenter?: string } /** Identity derived from a successful mint, used at connect time. */ @@ -40,6 +51,12 @@ export interface ClientCredentialAccountMintResult { * `instance_url`), forwarded to tools alongside the token. */ instanceUrl?: string + /** + * Data-center-scoped REST API base the minted token must be used against + * (Zoho Desk), forwarded to tools as their `apiDomain` param. Distinct from + * {@link instanceUrl} because the two reach different tool params. + */ + apiDomain?: string /** Scopes granted to the app, when the provider reports them. */ grantedScopes?: string[] identity?: ClientCredentialAccountIdentity @@ -74,6 +91,7 @@ const CLIENT_CREDENTIAL_ACCOUNT_MINTERS: Record< [ZOOM_SERVICE_ACCOUNT_PROVIDER_ID]: mintZoomServiceAccountToken, [BOX_SERVICE_ACCOUNT_PROVIDER_ID]: mintBoxServiceAccountToken, [SALESFORCE_SERVICE_ACCOUNT_PROVIDER_ID]: mintSalesforceServiceAccountToken, + [ZOHO_DESK_SERVICE_ACCOUNT_PROVIDER_ID]: mintZohoDeskServiceAccountToken, } export function getClientCredentialAccountMinter( @@ -95,6 +113,8 @@ export interface ClientCredentialAccountSecretBlob { clientId: string clientSecret: string orgId: string + /** Optional region selector; absent on every credential created before it existed. */ + dataCenter?: string metadata?: Record } diff --git a/apps/sim/lib/credentials/orchestration/index.ts b/apps/sim/lib/credentials/orchestration/index.ts index a26a190f126..ea69218e38c 100644 --- a/apps/sim/lib/credentials/orchestration/index.ts +++ b/apps/sim/lib/credentials/orchestration/index.ts @@ -5,9 +5,10 @@ import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' import { and, eq, sql } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { encryptSecret } from '@/lib/core/security/encryption' +import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { getCredentialActorContext } from '@/lib/credentials/access' import { AtlassianValidationError } from '@/lib/credentials/atlassian-service-account' +import { isClientCredentialAccountProviderId } from '@/lib/credentials/client-credential-accounts/descriptors' import { type CredentialDeleteReason, deleteCredential } from '@/lib/credentials/deletion' import { deleteWorkspaceEnvCredentials, @@ -22,6 +23,30 @@ import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('CredentialOrchestration') +/** + * Read the `dataCenter` already stored in a service-account credential's + * encrypted blob. Used on reconnect so a non-secret regional selector survives a + * secret rotation that does not resubmit it. Returns undefined on any failure - + * a blob that cannot be read must not block the reconnect, and the provider's + * own default then applies. + */ +async function readStoredDataCenter(credentialId: string): Promise { + try { + const rows = await db + .select({ key: credential.encryptedServiceAccountKey }) + .from(credential) + .where(eq(credential.id, credentialId)) + .limit(1) + const key = rows[0]?.key + if (!key) return undefined + const { decrypted } = await decryptSecret(key) + const blob = JSON.parse(decrypted) as { dataCenter?: unknown } + return typeof blob.dataCenter === 'string' && blob.dataCenter ? blob.dataCenter : undefined + } catch { + return undefined + } +} + export type CredentialOrchestrationErrorCode = | 'not_found' | 'forbidden' @@ -53,6 +78,7 @@ export interface PerformUpdateCredentialParams extends CredentialActorParams { clientId?: string clientSecret?: string orgId?: string + dataCenter?: string } export interface PerformCredentialResult { @@ -133,9 +159,25 @@ export async function performUpdateCredential( params.domain !== undefined || params.clientId !== undefined || params.clientSecret !== undefined || - params.orgId !== undefined + params.orgId !== undefined || + params.dataCenter !== undefined let rotatedSlackBotUserId: string | undefined if (hasRotationSecret && access.credential.type === 'service_account') { + // A reconnect rebuilds the secret blob from the submitted fields only, and + // the modal never prefills (secrets are never echoed back). For an actual + // secret that is correct - the admin retypes it. But a non-secret selector + // like the Zoho data center would be silently dropped, moving an EU/IN/AU + // credential back to the US accounts server. Carry the stored value forward + // when the caller did not supply one. + // Scoped to the providers that actually have a dataCenter field, so no + // other service-account reconnect (Slack, Atlassian, every token-paste + // provider) pays for a DB read plus a decrypt it can never use. + const carriedDataCenter = + params.dataCenter === undefined && + isClientCredentialAccountProviderId(access.credential.providerId ?? '') + ? await readStoredDataCenter(access.credential.id) + : params.dataCenter + try { const secret = await verifyAndBuildServiceAccountSecret( access.credential.providerId ?? '', @@ -147,6 +189,7 @@ export async function performUpdateCredential( clientId: params.clientId, clientSecret: params.clientSecret, orgId: params.orgId, + dataCenter: carriedDataCenter, } ) updates.encryptedServiceAccountKey = secret.encryptedServiceAccountKey diff --git a/apps/sim/lib/credentials/service-account-fields.ts b/apps/sim/lib/credentials/service-account-fields.ts index e636edde4cd..5502b86c1d5 100644 --- a/apps/sim/lib/credentials/service-account-fields.ts +++ b/apps/sim/lib/credentials/service-account-fields.ts @@ -16,6 +16,7 @@ export type ServiceAccountFieldId = | 'clientId' | 'clientSecret' | 'orgId' + | 'dataCenter' /** * Required create-body fields per service-account provider — the client-safe diff --git a/apps/sim/lib/credentials/service-account-secret.ts b/apps/sim/lib/credentials/service-account-secret.ts index 3562658a1d6..900462b6e27 100644 --- a/apps/sim/lib/credentials/service-account-secret.ts +++ b/apps/sim/lib/credentials/service-account-secret.ts @@ -43,6 +43,7 @@ export interface ServiceAccountSecretFields { clientId?: string clientSecret?: string orgId?: string + dataCenter?: string } export interface ServiceAccountSecretResult { @@ -234,19 +235,24 @@ async function buildClientCredentialAccountSecret( const clientId = fields.clientId?.trim() const clientSecret = fields.clientSecret?.trim() const orgId = fields.orgId?.trim() + const dataCenter = fields.dataCenter?.trim() if (!clientId || !clientSecret || !orgId) { - const required = descriptor.fields.map((field) => field.id).join(', ') + const required = descriptor.fields + .filter((field) => !field.optional) + .map((field) => field.id) + .join(', ') throw new ServiceAccountSecretError( `${required} are required for ${descriptor.serviceLabel} service account credentials` ) } - const mint = await minter({ clientId, clientSecret, orgId }) + const mint = await minter({ clientId, clientSecret, orgId, dataCenter }) const blob: ClientCredentialAccountSecretBlob = { type: CLIENT_CREDENTIAL_ACCOUNT_SECRET_TYPE, providerId, clientId, clientSecret, orgId, + ...(dataCenter ? { dataCenter } : {}), ...(mint.identity?.storedMetadata ? { metadata: mint.identity.storedMetadata } : {}), } const { encrypted } = await encryptSecret(JSON.stringify(blob)) diff --git a/apps/sim/lib/integrations/credential-display.test.ts b/apps/sim/lib/integrations/credential-display.test.ts index 5305a30066f..0a961eed772 100644 --- a/apps/sim/lib/integrations/credential-display.test.ts +++ b/apps/sim/lib/integrations/credential-display.test.ts @@ -70,6 +70,7 @@ const EXPECTED_COVERAGE: Record = { 'trello-service-account': ['trello'], 'wealthbox-service-account': ['wealthbox'], 'webflow-service-account': ['webflow'], + 'zoho-desk-service-account': ['zoho-desk'], 'zoom-service-account': ['zoom'], } diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index ff7c8965ac8..628a6dd91be 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -246,6 +246,7 @@ import { ZendeskIcon, ZepIcon, ZeroBounceIcon, + ZohoDeskIcon, ZoomIcon, ZoomInfoIcon, } from '@/components/icons' @@ -511,6 +512,7 @@ export const blockTypeToIconMap: Record = { zendesk: ZendeskIcon, zep: ZepIcon, zerobounce: ZeroBounceIcon, + zoho_desk: ZohoDeskIcon, zoom: ZoomIcon, zoominfo: ZoomInfoIcon, } diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index e64dc721f55..db30b1d2064 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -21812,6 +21812,72 @@ "integrationType": "sales", "tags": ["enrichment", "sales-engagement"] }, + { + "type": "zoho_desk", + "slug": "zoho-desk", + "name": "Zoho Desk", + "description": "Manage Zoho Desk tickets, comments, threads, and contacts", + "longDescription": "Read and update Zoho Desk tickets, manage comments and threads, look up contacts, and download attachments. Can also trigger workflows from Zoho Desk webhook events.", + "bgColor": "#E42527", + "iconName": "ZohoDeskIcon", + "docsUrl": "https://docs.sim.ai/integrations/zoho_desk", + "operations": [ + { + "name": "List Tickets", + "description": "List tickets from a Zoho Desk organization with optional filters. Returns a list projection: description, resolution, statusType and classification are only available from Get Ticket." + }, + { + "name": "Get Ticket", + "description": "Retrieve a single Zoho Desk ticket by ID." + }, + { + "name": "Update Ticket", + "description": "Update fields on an existing Zoho Desk ticket." + }, + { + "name": "List Comments", + "description": "List comments on a Zoho Desk ticket." + }, + { + "name": "Add Comment", + "description": "Add a comment to a Zoho Desk ticket." + }, + { + "name": "List Threads", + "description": "List conversation threads on a Zoho Desk ticket, newest first (Zoho sorts by sendDateTime descending by default). Returns a list projection: message bodies (content, summary, to/cc/bcc) come back only from Get Thread." + }, + { + "name": "Get Thread", + "description": "Retrieve the full content of a single Zoho Desk ticket thread." + }, + { + "name": "Get Contact", + "description": "Retrieve a Zoho Desk contact by ID." + }, + { + "name": "Get Attachment", + "description": "Download a Zoho Desk ticket attachment (from its href) as a file." + }, + { + "name": "List Organizations", + "description": "List the Zoho Desk organizations (portals) the connected account can access." + } + ], + "operationCount": 10, + "triggers": [ + { + "id": "zoho_desk", + "name": "Zoho Desk Event", + "description": "Trigger a workflow when a Zoho Desk event occurs (ticket, comment, thread, contact, agent, task, or article changes)." + } + ], + "triggerCount": 1, + "authType": "oauth", + "oauthServiceId": "zoho-desk", + "category": "tools", + "integrationType": "support", + "tags": ["customer-support", "ticketing", "automation"] + }, { "type": "zoom", "slug": "zoom", diff --git a/apps/sim/lib/oauth/oauth.test.ts b/apps/sim/lib/oauth/oauth.test.ts index d6273e5af40..6211943de41 100644 --- a/apps/sim/lib/oauth/oauth.test.ts +++ b/apps/sim/lib/oauth/oauth.test.ts @@ -45,6 +45,8 @@ beforeAll(() => { LINKEDIN_CLIENT_SECRET: 'linkedin_client_secret', SALESFORCE_CLIENT_ID: 'salesforce_client_id', SALESFORCE_CLIENT_SECRET: 'salesforce_client_secret', + ZOHO_CLIENT_ID: 'zoho_client_id', + ZOHO_CLIENT_SECRET: 'zoho_client_secret', SHOPIFY_CLIENT_ID: 'shopify_client_id', SHOPIFY_CLIENT_SECRET: 'shopify_client_secret', ZOOM_CLIENT_ID: 'zoom_client_id', diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 5fc37bbe756..2cd452358d0 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -55,6 +55,7 @@ import { WebflowIcon, WordpressIcon, xIcon, + ZohoDeskIcon, ZoomIcon, } from '@/components/icons' import { env } from '@/lib/core/config/env' @@ -1098,6 +1099,48 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'salesforce', }, + 'zoho-desk': { + name: 'Zoho Desk', + icon: ZohoDeskIcon, + services: { + 'zoho-desk': { + name: 'Zoho Desk', + description: + 'Manage Zoho Desk tickets, comments, threads, and contacts. Connecting with OAuth requires a Zoho account in the US data center; a Self Client also supports the EU, IN, and AU data centers.', + providerId: 'zoho-desk', + serviceAccountProviderId: 'zoho-desk-service-account', + icon: ZohoDeskIcon, + baseProviderIcon: ZohoDeskIcon, + // Kept to exactly what the tools and the webhook trigger exercise: + // tickets (incl. threads/comments), contacts (get_contact), basic + // (list_organizations), agents (the `assigneeId` picker lists agents), + // webhook create/delete (the trigger provisions and tears down its own + // subscription), and profile (OAuth getUserInfo). + // Desk.search.READ, Desk.webhooks.READ and Desk.webhooks.UPDATE were + // requested but unused - no tool searches, and the provider never lists + // or edits a subscription. + scopes: [ + // READ + UPDATE rather than tickets.ALL: no tool creates or deletes a + // ticket, and ALL additionally grants ticket DELETE. Threads, comments + // and attachments live under the tickets module and are covered by + // these two. NOTE: Zoho publishes no scope line for the attachment + // content sub-path - verify attachment download against a live account + // before merge and widen here if it returns SCOPE_MISMATCH. + 'Desk.tickets.READ', + 'Desk.tickets.UPDATE', + 'Desk.contacts.READ', + // READ only: the agent picker for `assigneeId` lists agents, and no + // tool creates, edits or deletes one. + 'Desk.agents.READ', + 'Desk.basic.READ', + 'Desk.webhooks.CREATE', + 'Desk.webhooks.DELETE', + 'aaaserver.profile.READ', + ], + }, + }, + defaultService: 'zoho-desk', + }, zoom: { name: 'Zoom', icon: ZoomIcon, @@ -1608,6 +1651,21 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: false, } } + case 'zoho-desk': { + // Zoho's refresh_token grant returns a new access token but no new refresh + // token, so rotation stays off (the existing refresh token is preserved). + // The refresh must target the accounts server; a US/multi-DC-enabled client + // uses accounts.zoho.com. Data residency for API calls is honored separately + // via the persisted Desk base URL derived from the token response api_domain. + const { clientId, clientSecret } = getCredentials(env.ZOHO_CLIENT_ID, env.ZOHO_CLIENT_SECRET) + return { + tokenEndpoint: 'https://accounts.zoho.com/oauth/v2/token', + clientId, + clientSecret, + useBasicAuth: false, + supportsRefreshTokenRotation: false, + } + } default: throw new Error(`Unsupported provider: ${provider}`) } @@ -1850,7 +1908,12 @@ export async function refreshOAuthToken( const expiresIn = data.expires_in || data.expiresIn || 3600 if (!accessToken) { - logger.warn('No access token found in refresh response', { providerId, response: data }) + // Log only the shape, never `data` itself - on a partial success it can + // carry live tokens. + logger.warn('No access token found in refresh response', { + providerId, + responseKeys: Object.keys(data ?? {}), + }) return { ok: false, message: 'No access token in refresh response' } } diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 40c24fe8547..4a036e17dbe 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -85,6 +85,7 @@ export type OAuthProvider = | 'spotify' | 'calcom' | 'docusign' + | 'zoho-desk' export type OAuthService = | 'google' @@ -141,6 +142,7 @@ export type OAuthService = | 'docusign' | 'github' | 'monday' + | 'zoho-desk' export interface OAuthProviderConfig { name: string diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 141796ba4fa..b0df084e0c4 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -11,6 +11,15 @@ import type { * Used by the OAuth Required Modal and available for any UI that needs to display scope info. */ export const SCOPE_DESCRIPTIONS: Record = { + // Zoho Desk scopes + 'Desk.tickets.READ': 'View tickets, threads, comments, and attachments', + 'Desk.tickets.UPDATE': 'Update tickets and add comments', + 'Desk.contacts.READ': 'View contacts', + 'Desk.agents.READ': 'View agents', + 'Desk.basic.READ': 'View basic account and organization data', + 'Desk.webhooks.CREATE': 'Create webhooks', + 'Desk.webhooks.DELETE': 'Delete webhooks', + 'aaaserver.profile.READ': 'View your Zoho profile', // Google scopes 'https://www.googleapis.com/auth/gmail.send': 'Send emails', 'https://www.googleapis.com/auth/gmail.labels': 'View and manage email labels', diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index 7bb32655690..b5e04b99df2 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -683,7 +683,10 @@ export async function prepareStableTriggerWebhooksForDeploy({ success: false, error: { message: getErrorMessage(error, 'Failed to prepare webhook registrations'), - status: 500, + // Propagate a provider-attached status (e.g. Zoho's 4xx edition/validation + // failures) so the deploy outbox fails terminally instead of retrying, + // matching the legacy save path's status-aware mapping below. + status: (error as { status?: number })?.status ?? 500, }, } } @@ -940,7 +943,11 @@ export async function saveTriggerWebhooksForDeploy({ (cleanupFailure as Error)?.message || (error as Error)?.message || 'Failed to create external subscription', - status: 500, + // Propagate a 4xx from the provider handler (e.g. a permanent Zoho + // config/permission/invalid-data failure) so the outbox classifies it + // as non-retryable; anything else (network, provider 5xx) stays 500 and + // retryable. cleanupFailure never overrides the root cause's status. + status: (error as { status?: number })?.status ?? 500, }, } } diff --git a/apps/sim/lib/webhooks/pending-verification.ts b/apps/sim/lib/webhooks/pending-verification.ts index 026b6b82ccc..522ae8c4574 100644 --- a/apps/sim/lib/webhooks/pending-verification.ts +++ b/apps/sim/lib/webhooks/pending-verification.ts @@ -49,6 +49,10 @@ const pendingWebhookVerificationRegistrationMatchers: Record< grain: () => true, generic: (registration) => registration.metadata?.verifyTestEvents === true, salesforce: () => true, + // Zoho Desk validates the notification URL with a create-time probe that must + // return 200 before it will register the subscription (chicken-and-egg: the + // webhook row is inactive until the create succeeds). + zoho_desk: () => true, } const pendingWebhookVerificationProbeMatchers: Record< @@ -68,6 +72,14 @@ const pendingWebhookVerificationProbeMatchers: Record< method === 'GET' || method === 'HEAD' || (method === 'POST' && (!body || Object.keys(body).length === 0)), + // Zoho Desk sends a GET reachability probe at subscription-create time. + // Zoho sends a GET reachability probe at subscription-create time and, if that + // does not return 200, falls back to a POST probe before failing the create. + // Match the empty-bodied POST too, as grain/generic/salesforce do. + zoho_desk: ({ method, body }) => + method === 'GET' || + method === 'HEAD' || + (method === 'POST' && (!body || Object.keys(body).length === 0)), } function getRedisKey(path: string): string { diff --git a/apps/sim/lib/webhooks/provider-subscriptions.ts b/apps/sim/lib/webhooks/provider-subscriptions.ts index 35cad91fafa..615a5ea7722 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.ts @@ -37,6 +37,10 @@ const SYSTEM_MANAGED_FIELDS = new Set([ 'setupCompleted', 'subscriptionExpiration', 'userId', + // Zoho Desk provider-managed: the persisted data-center Desk base, set by + // createSubscription (not a user trigger field), so it must not count as a + // config change that forces delete/recreate. + 'apiDomain', ]) /** diff --git a/apps/sim/lib/webhooks/providers/registry.ts b/apps/sim/lib/webhooks/providers/registry.ts index ac89b403fa8..085280c9d55 100644 --- a/apps/sim/lib/webhooks/providers/registry.ts +++ b/apps/sim/lib/webhooks/providers/registry.ts @@ -58,6 +58,7 @@ import { vercelHandler } from '@/lib/webhooks/providers/vercel' import { webflowHandler } from '@/lib/webhooks/providers/webflow' import { whatsappHandler } from '@/lib/webhooks/providers/whatsapp' import { zendeskHandler } from '@/lib/webhooks/providers/zendesk' +import { zohoDeskHandler } from '@/lib/webhooks/providers/zoho-desk' import { zoomHandler } from '@/lib/webhooks/providers/zoom' const logger = createLogger('WebhookProviderRegistry') @@ -122,6 +123,7 @@ const PROVIDER_HANDLERS: Record = { webflow: webflowHandler, whatsapp: whatsappHandler, zendesk: zendeskHandler, + zoho_desk: zohoDeskHandler, zoom: zoomHandler, } diff --git a/apps/sim/lib/webhooks/providers/zoho-desk.test.ts b/apps/sim/lib/webhooks/providers/zoho-desk.test.ts new file mode 100644 index 00000000000..969c2672134 --- /dev/null +++ b/apps/sim/lib/webhooks/providers/zoho-desk.test.ts @@ -0,0 +1,456 @@ +/** + * @vitest-environment node + */ +import { afterEach, describe, expect, it, vi } from 'vitest' + +vi.mock('@/app/api/auth/oauth/utils', () => ({ + refreshAccessTokenIfNeeded: vi.fn(), +})) + +vi.mock('@/lib/webhooks/provider-subscription-utils', () => ({ + getCredentialOwner: vi.fn(), + getNotificationUrl: vi.fn(() => 'https://example.com/api/webhooks/trigger/path'), +})) + +import { + matchesPendingWebhookVerificationProbe, + requiresPendingWebhookVerification, +} from '@/lib/webhooks/pending-verification' +import { getCredentialOwner } from '@/lib/webhooks/provider-subscription-utils' +import { mapZohoWebhookError, zohoDeskHandler } from '@/lib/webhooks/providers/zoho-desk' +import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' + +function errorStatus(err: unknown): number | undefined { + return (err as { status?: number })?.status +} + +function makeAuthContext(headers: Record, providerConfig: Record) { + return { + webhook: {}, + workflow: {}, + request: { headers: new Headers(headers) } as unknown as Request, + rawBody: '', + requestId: 'test', + providerConfig, + } +} + +describe('zohoDeskHandler', () => { + it('acknowledges ingress via the durable queue (5s deadline)', () => { + expect(zohoDeskHandler.executionMode).toBe('queue') + }) + + describe('verifyAuth', () => { + afterEach(() => { + vi.mocked(getCredentialOwner).mockReset() + }) + + it('rejects requests without the X-ZDesk-JWT header', async () => { + const result = await zohoDeskHandler.verifyAuth?.( + // biome-ignore lint/suspicious/noExplicitAny: minimal context for the header-only path + makeAuthContext({}, { orgId: '1', webhookId: '2' }) as any + ) + expect(result).not.toBeNull() + expect(result?.status).toBe(401) + }) + + it('falls back to the credential Desk domain when the webhook row has no apiDomain', async () => { + vi.mocked(getCredentialOwner).mockResolvedValue({ + accountId: 'acct-1', + userId: 'u1', + // biome-ignore lint/suspicious/noExplicitAny: partial owner shape is enough for this path + } as any) + await zohoDeskHandler.verifyAuth?.( + makeAuthContext( + { 'x-zdesk-jwt': 'not-a-real-jwt' }, + { orgId: '1', externalId: '2', credentialId: 'cred-1' } + // biome-ignore lint/suspicious/noExplicitAny: minimal context for the fallback path + ) as any + ) + expect(getCredentialOwner).toHaveBeenCalledWith('cred-1', 'test') + }) + + it('uses the persisted apiDomain without a credential lookup (fast path)', async () => { + await zohoDeskHandler.verifyAuth?.( + makeAuthContext( + { 'x-zdesk-jwt': 'not-a-real-jwt' }, + { orgId: '1', externalId: '2', credentialId: 'cred-1', apiDomain: 'https://desk.zoho.eu' } + // biome-ignore lint/suspicious/noExplicitAny: minimal context for the fast path + ) as any + ) + expect(getCredentialOwner).not.toHaveBeenCalled() + }) + }) + + describe('createSubscription', () => { + async function captureCreateError(providerConfig: Record): Promise { + try { + await zohoDeskHandler.createSubscription?.({ + webhook: { providerConfig }, + workflow: {}, + userId: 'user-1', + requestId: 'test', + // biome-ignore lint/suspicious/noExplicitAny: request is unused on these guard paths + request: {} as any, + }) + } catch (error) { + return error + } + return undefined + } + + it('fails terminally (400) when the organization ID is missing', async () => { + const error = await captureCreateError({ eventType: 'Ticket_Add' }) + expect((error as Error)?.message).toMatch(/Organization ID/i) + expect(errorStatus(error)).toBe(400) + }) + + it('fails terminally (400) when the event type is missing', async () => { + const error = await captureCreateError({ orgId: '700123' }) + expect((error as Error)?.message).toMatch(/event type/i) + expect(errorStatus(error)).toBe(400) + }) + + it('accepts the manual organization field when the canonical orgId is absent', async () => { + // Reaching the event-type guard proves the organization guard passed, i.e. + // `manualOrgId` resolved. See resolveConfigOrgId: the deploy-time canonical + // collapse normally writes `orgId`, but drops it when the pair is pinned to + // basic mode while only the manual field carries a value. + const error = await captureCreateError({ manualOrgId: '700123' }) + expect((error as Error)?.message).toMatch(/event type/i) + expect(errorStatus(error)).toBe(400) + }) + + it('prefers the collapsed canonical orgId over the manual field', async () => { + const error = await captureCreateError({ orgId: '', manualOrgId: ' ' }) + expect((error as Error)?.message).toMatch(/Organization ID/i) + expect(errorStatus(error)).toBe(400) + }) + }) + + describe('formatInput', () => { + it('maps a Zoho Desk event array to the trigger outputs', async () => { + const result = await zohoDeskHandler.formatInput?.({ + webhook: {}, + workflow: { id: 'wf', userId: 'user' }, + body: [ + { + eventType: 'Ticket_Add', + eventTime: '1700000000000', + orgId: '700123', + payload: { id: 'ticket-1' }, + prevState: null, + }, + ], + headers: {}, + requestId: 'test', + }) + expect(result?.input).toMatchObject({ + eventType: 'Ticket_Add', + eventTime: '1700000000000', + orgId: '700123', + payload: { id: 'ticket-1' }, + }) + }) + + it('passes through a non-array body unchanged', async () => { + const body = { unexpected: true } + const result = await zohoDeskHandler.formatInput?.({ + webhook: {}, + workflow: { id: 'wf', userId: 'user' }, + body, + headers: {}, + requestId: 'test', + }) + expect(result?.input).toBe(body) + }) + + it('emits a normalized null shape for an empty array instead of leaking []', async () => { + const result = await zohoDeskHandler.formatInput?.({ + webhook: {}, + workflow: { id: 'wf', userId: 'user' }, + body: [], + headers: {}, + requestId: 'test', + }) + expect(result?.input).toEqual({ + eventType: null, + eventTime: null, + orgId: null, + payload: null, + prevState: null, + }) + }) + + it('derives a plain-text contentText for html comment/thread payloads', async () => { + const result = await zohoDeskHandler.formatInput?.({ + webhook: {}, + workflow: { id: 'wf', userId: 'user' }, + body: [ + { + eventType: 'Ticket_Comment_Add', + eventTime: '1700000000000', + orgId: '700123', + payload: { + id: 'comment-1', + content: '
testing
', + contentType: 'html', + }, + prevState: { id: 'comment-1', content: '
before
', contentType: 'html' }, + }, + ], + headers: {}, + requestId: 'test', + }) + const input = result?.input as { + payload: Record + prevState: Record + } + expect(input.payload.content).toBe('
testing
') + expect(input.payload.contentType).toBe('html') + expect(input.payload.contentText).toBe('testing') + // prevState is enriched symmetrically so before/after comparisons match shapes. + expect(input.prevState.content).toBe('
before
') + expect(input.prevState.contentText).toBe('before') + }) + + it('mirrors plainText content into contentText', async () => { + const result = await zohoDeskHandler.formatInput?.({ + webhook: {}, + workflow: { id: 'wf', userId: 'user' }, + body: [ + { + eventType: 'Ticket_Comment_Add', + eventTime: '1700000000000', + orgId: '700123', + payload: { id: 'comment-2', content: 'just text', contentType: 'plainText' }, + prevState: null, + }, + ], + headers: {}, + requestId: 'test', + }) + const payload = (result?.input as { payload: Record }).payload + expect(payload.contentText).toBe('just text') + expect(payload.content).toBe('just text') + }) + }) + + describe('createSubscription request body', () => { + afterEach(() => { + vi.restoreAllMocks() + vi.mocked(getCredentialOwner).mockReset() + vi.mocked(refreshAccessTokenIfNeeded).mockReset() + }) + + it('omits ignoreSourceId (Zoho rejects an arbitrary UUID) and returns no such id', async () => { + vi.mocked(getCredentialOwner).mockResolvedValue({ + accountId: 'acc-1', + userId: 'user-1', + // biome-ignore lint/suspicious/noExplicitAny: partial owner shape for the test + } as any) + vi.mocked(refreshAccessTokenIfNeeded).mockResolvedValue('zoho-token') + + let sentBody: Record = {} + vi.spyOn(globalThis, 'fetch').mockImplementation(async (_url, init) => { + sentBody = JSON.parse(String((init as RequestInit).body)) + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ id: 'wh-123' }), + } as unknown as Response + }) + + const result = await zohoDeskHandler.createSubscription?.({ + webhook: { + id: 'w1', + path: 'p1', + providerConfig: { credentialId: 'cred-1', orgId: '700123', eventType: 'Ticket_Add' }, + }, + workflow: {}, + userId: 'user-1', + requestId: 'test', + // biome-ignore lint/suspicious/noExplicitAny: request is unused on this path + request: {} as any, + }) + + expect(sentBody).not.toHaveProperty('ignoreSourceId') + expect(sentBody.subscriptions).toHaveProperty('Ticket_Add') + expect(sentBody.isEnabled).toBe(true) + expect(result?.providerConfigUpdates).toMatchObject({ externalId: 'wh-123' }) + expect(result?.providerConfigUpdates).not.toHaveProperty('ignoreSourceId') + }) + + /** Drives createSubscription and returns the JSON body sent to Zoho. */ + async function captureSentBody( + providerConfig: Record + ): Promise> { + vi.mocked(getCredentialOwner).mockResolvedValue({ + accountId: 'acc-1', + userId: 'user-1', + // biome-ignore lint/suspicious/noExplicitAny: partial owner shape for the test + } as any) + vi.mocked(refreshAccessTokenIfNeeded).mockResolvedValue('zoho-token') + + let sentBody: Record = {} + vi.spyOn(globalThis, 'fetch').mockImplementation(async (_url, init) => { + sentBody = JSON.parse(String((init as RequestInit).body)) + return { + ok: true, + status: 200, + text: async () => JSON.stringify({ id: 'wh-123' }), + } as unknown as Response + }) + + await zohoDeskHandler.createSubscription?.({ + webhook: { + id: 'w1', + path: 'p1', + providerConfig: { credentialId: 'cred-1', orgId: '700123', ...providerConfig }, + }, + workflow: {}, + userId: 'user-1', + requestId: 'test', + // biome-ignore lint/suspicious/noExplicitAny: request is unused on this path + request: {} as any, + }) + return sentBody + } + + // Zoho documents includePrevState on the Ticket/Contact/Agent/Task/Article + // update events but NOT on Ticket_Comment_Update, which lists only + // departmentIds. An `endsWith('_Update')` rule sent an undocumented filter + // key on that one event. + it.each(['Ticket_Update', 'Contact_Update', 'Agent_Update', 'Task_Update', 'Article_Update'])( + 'sets includePrevState for %s', + async (eventType) => { + const body = await captureSentBody({ eventType }) + expect((body.subscriptions as Record)[eventType]).toMatchObject({ + includePrevState: true, + }) + } + ) + + it('does NOT set includePrevState for Ticket_Comment_Update', async () => { + // With a department filter the object exists, so this proves the key is + // absent rather than the whole filter being null for an unrelated reason. + const withDepts = await captureSentBody({ + eventType: 'Ticket_Comment_Update', + triggerDepartmentIds: '111', + }) + expect((withDepts.subscriptions as Record).Ticket_Comment_Update).toEqual({ + departmentIds: ['111'], + }) + + // And with no filters at all it collapses to null, not `{includePrevState:true}`. + const bare = await captureSentBody({ eventType: 'Ticket_Comment_Update' }) + expect((bare.subscriptions as Record).Ticket_Comment_Update).toBeNull() + }) + + // Zoho: events outside the ticket/task family "do not support filters. + // Therefore, pass the value as null in the API request." + it('drops departmentIds for an event whose filter does not accept it', async () => { + const body = await captureSentBody({ + eventType: 'Contact_Add', + triggerDepartmentIds: '111,222', + }) + expect((body.subscriptions as Record).Contact_Add).toBeNull() + }) + + it('keeps departmentIds for an event whose filter accepts it', async () => { + const body = await captureSentBody({ + eventType: 'Ticket_Add', + triggerDepartmentIds: '111,222', + }) + expect((body.subscriptions as Record).Ticket_Add).toEqual({ + departmentIds: ['111', '222'], + }) + }) + + it('sends null, not an empty object, when an event has no filters', async () => { + const body = await captureSentBody({ eventType: 'Ticket_Delete' }) + expect((body.subscriptions as Record).Ticket_Delete).toBeNull() + }) + }) + + describe('mapZohoWebhookError', () => { + it('surfaces INVALID_DATA field errors and is non-retryable (4xx)', () => { + const err = mapZohoWebhookError( + 422, + JSON.stringify({ + errorCode: 'INVALID_DATA', + errors: [ + { + fieldName: '/ignoreSourceId', + errorMessage: + "The value passed for field '/ignoreSourceId' does not match the allowed values.", + }, + ], + }) + ) + expect(err.message).toContain('INVALID_DATA') + expect(err.message).toContain('/ignoreSourceId') + expect(err.message).not.toContain('Professional edition') + expect(errorStatus(err)).toBe(422) + }) + + it('surfaces UNPROCESSABLE_ENTITY (URL validation) verbatim, non-retryable', () => { + const err = mapZohoWebhookError( + 422, + JSON.stringify({ + errorCode: 'UNPROCESSABLE_ENTITY', + message: + 'Validation failed for the condition : The endpoint failed to respond with status code 200', + }) + ) + expect(err.message).toContain('endpoint failed to respond with status code 200') + expect(err.message).not.toContain('Professional edition') + expect(errorStatus(err)).toBe(422) + }) + + it('only claims the edition/permission cause when Zoho indicates it', () => { + const err = mapZohoWebhookError( + 403, + JSON.stringify({ errorCode: 'PERMISSION_DENIED', message: 'You do not have permission' }) + ) + expect(err.message).toContain('permission') + expect(err.message).toContain('Professional edition') + expect(errorStatus(err)).toBe(403) + }) + + it('does not add the edition hint to a 403 whose body is unrelated to edition/permission', () => { + const err = mapZohoWebhookError( + 403, + JSON.stringify({ errorCode: 'INVALID_OAUTH', message: 'Invalid OAuth token' }) + ) + expect(err.message).toContain('INVALID_OAUTH') + expect(err.message).toContain('Invalid OAuth token') + expect(err.message).not.toContain('Professional edition') + expect(errorStatus(err)).toBe(403) + }) + + it('keeps provider 5xx retryable', () => { + const err = mapZohoWebhookError(500, JSON.stringify({ errorCode: 'INTERNAL_ERROR' })) + expect(errorStatus(err)).toBe(503) + }) + }) + + describe('create-time URL verification probe', () => { + it('opts Zoho Desk into pending webhook verification', () => { + expect(requiresPendingWebhookVerification('zoho_desk')).toBe(true) + }) + + it('answers Zoho GET/HEAD probes but not real POST deliveries', () => { + const entry = { provider: 'zoho_desk', path: 'p1', expiresAt: Date.now() + 60_000 } + expect( + matchesPendingWebhookVerificationProbe(entry, { method: 'GET', body: undefined }) + ).toBe(true) + expect( + matchesPendingWebhookVerificationProbe(entry, { method: 'HEAD', body: undefined }) + ).toBe(true) + expect( + matchesPendingWebhookVerificationProbe(entry, { method: 'POST', body: { eventType: 'x' } }) + ).toBe(false) + }) + }) +}) diff --git a/apps/sim/lib/webhooks/providers/zoho-desk.ts b/apps/sim/lib/webhooks/providers/zoho-desk.ts new file mode 100644 index 00000000000..a70935d4385 --- /dev/null +++ b/apps/sim/lib/webhooks/providers/zoho-desk.ts @@ -0,0 +1,522 @@ +import { db } from '@sim/db' +import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { truncate } from '@sim/utils/string' +import { eq } from 'drizzle-orm' +import * as jose from 'jose' +import { NextResponse } from 'next/server' +import { getCredentialOwner, getNotificationUrl } from '@/lib/webhooks/provider-subscription-utils' +import type { + AuthContext, + DeleteSubscriptionContext, + FormatInputContext, + FormatInputResult, + SubscriptionContext, + SubscriptionResult, + WebhookProviderHandler, +} from '@/lib/webhooks/providers/types' +import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { isZohoHost } from '@/tools/zoho_desk/host-allowlist' +import { withDerivedContentText } from '@/tools/zoho_desk/utils' + +const logger = createLogger('WebhookProvider:ZohoDesk') + +const DEFAULT_ZOHO_DESK_BASE = 'https://desk.zoho.com' +// Stop at a comma or whitespace: better-auth persists Zoho's scopes comma-joined +// (no spaces), so a greedy `\S+` would swallow the whole scope list into the host. +const ZOHO_DESK_BASE_URL_REGEX = /__zoho_domain__:([^\s,]+)/ + +/** + * Remote JWKS sets are cached per Desk data-center host. `createRemoteJWKSet` + * caches keys and coalesces refetches internally, so a module-scoped cache keeps + * verification within Zoho's 5-second delivery deadline. + */ +const jwksCache = new Map>() + +/** + * Events whose subscription filter accepts `departmentIds`. Zoho documents the + * rest as taking no filter at all. + */ +const DEPARTMENT_FILTERABLE_EVENTS = new Set([ + 'Ticket_Add', + 'Ticket_Update', + 'Ticket_Comment_Add', + 'Ticket_Comment_Update', + 'Ticket_Thread_Add', + 'Task_Add', + 'Task_Update', +]) + +/** + * Events whose filter accepts `includePrevState`. Enumerated rather than derived + * from an `_Update` suffix: Zoho documents the attribute on Ticket/Contact/Agent/ + * Task/Article update events but NOT on `Ticket_Comment_Update`, which lists only + * `departmentIds`. Sending an undocumented filter key on a live create is the + * same class of risk as an undocumented query param. + */ +const PREV_STATE_EVENTS = new Set([ + 'Ticket_Update', + 'Contact_Update', + 'Agent_Update', + 'Task_Update', + 'Article_Update', +]) + +/** + * Bound on distinct Desk hosts held in {@link jwksCache}. The host is derived + * from `providerConfig.apiDomain`, which `SYSTEM_MANAGED_FIELDS` protects from + * *diffing* but not from being written by a workspace member. `safeZohoDeskBase` + * already clamps it to a Zoho apex so no key material is ever fetched off-Zoho, + * but any `*.zoho.com` label still passes - so without a cap, repeated writes + * plus webhook hits could grow one JWKS instance (and its key cache) per label. + * Zoho has a handful of data centers; anything beyond this is not legitimate + * traffic, so evicting oldest-first is safe. + */ +const JWKS_CACHE_MAX_ENTRIES = 16 + +function getJwks(deskHost: string): ReturnType { + const set = jwksCache.get(deskHost) + if (set) return set + + if (jwksCache.size >= JWKS_CACHE_MAX_ENTRIES) { + const oldest = jwksCache.keys().next() + if (!oldest.done) jwksCache.delete(oldest.value) + } + // Zoho fails a delivery that is not answered within 5 seconds and publishes no + // retry, so a cold-start JWKS fetch must not consume the whole budget - + // jose's default timeoutDuration is 5000ms, exactly the deadline. + const created = jose.createRemoteJWKSet(new URL(`https://${deskHost}/.well-known/jwks.json`), { + timeoutDuration: 1500, + }) + jwksCache.set(deskHost, created) + return created +} + +/** + * Anchor a Desk base URL to the Zoho apex allowlist before it is used to build a + * token-carrying request or a JWKS fetch. `providerConfig.apiDomain` is only + * written by `createSubscription`, but `SYSTEM_MANAGED_FIELDS` governs config + * *diffing*, not writability - it does not strip a caller-supplied `apiDomain` + * from an incoming providerConfig. Falling back to the US base on anything + * unrecognized keeps a hostile value from ever receiving the OAuth token or + * standing in as the JWKS issuer. + */ +function safeZohoDeskBase(candidate: unknown): string { + if (typeof candidate !== 'string' || !candidate) return DEFAULT_ZOHO_DESK_BASE + try { + const url = new URL(candidate) + if (url.protocol !== 'https:' || !isZohoHost(url.hostname)) return DEFAULT_ZOHO_DESK_BASE + return candidate.replace(/\/+$/, '') + } catch { + return DEFAULT_ZOHO_DESK_BASE + } +} + +/** Read the persisted data-center Desk base URL from the credential's scope marker. */ +async function resolveZohoDeskApiDomain(accountId: string): Promise { + try { + const rows = await db + .select({ scope: account.scope }) + .from(account) + .where(eq(account.id, accountId)) + .limit(1) + const scope = rows[0]?.scope + const match = typeof scope === 'string' ? scope.match(ZOHO_DESK_BASE_URL_REGEX) : null + return safeZohoDeskBase(match?.[1]) + } catch (error) { + logger.warn('Failed to resolve Zoho Desk api domain from credential', { + message: toError(error).message, + }) + return DEFAULT_ZOHO_DESK_BASE + } +} + +function splitCsv(value: unknown): string[] { + if (typeof value !== 'string') return [] + return value + .split(',') + .map((item) => item.trim()) + .filter(Boolean) +} + +/** + * Resolve the organization id out of a persisted `providerConfig`. + * + * The trigger exposes the organization as a canonical basic/advanced pair: + * `orgId` (the selector) and `manualOrgId` (free text). `buildProviderConfig` + * collapses that pair at deploy time and writes the ACTIVE member's value under + * the canonical key `orgId`, so that is the authoritative read and is checked + * first. + * + * `manualOrgId` is the explicit fallback because the collapse is strict about + * the resolved mode: when `block.data.canonicalModes` pins the group to `basic` + * while only the manual field carries a value, the collapse deletes the + * canonical key even though the deploy-time required-field check passes (the + * canonical group counts as satisfied by the manual member). Without this + * fallback that combination would deploy and then fail here with "Organization + * ID is required". + */ +function resolveConfigOrgId(config: Record): string | undefined { + if (typeof config.orgId === 'string' && config.orgId.trim()) return config.orgId.trim() + if (typeof config.manualOrgId === 'string' && config.manualOrgId.trim()) { + return config.manualOrgId.trim() + } + return undefined +} + +/** Error carrying an HTTP status so the deploy outbox can classify retryability. */ +function statusError(message: string, status: number): Error { + const err = new Error(message) as Error & { status: number } + err.status = status + return err +} + +/** Zoho errorCode / message patterns that genuinely indicate a permission or edition denial. */ +const ZOHO_EDITION_PERMISSION_PATTERN = + /permission|not\s+(allowed|permitted|supported)|edition|upgrade|feature/i + +/** + * Map a Zoho Desk webhook-creation failure to an actionable error that surfaces + * Zoho's real errorCode / message / field errors instead of a catch-all. The + * returned error carries an HTTP `status`: permanent client failures (4xx - + * invalid data, permission, unprocessable) keep their 4xx so the deploy outbox + * fails the deploy terminally (NonRetryableDeploymentError) with the true + * reason; rate limits and provider 5xx map to 5xx so they stay retryable. + */ +export function mapZohoWebhookError(status: number, bodyText: string): Error { + let errorCode: string | undefined + let message: string | undefined + let fieldErrors: string[] = [] + try { + const parsed = JSON.parse(bodyText) as { + errorCode?: unknown + message?: unknown + errors?: Array<{ fieldName?: unknown; errorMessage?: unknown }> + } + if (typeof parsed.errorCode === 'string') errorCode = parsed.errorCode + if (typeof parsed.message === 'string') message = parsed.message + if (Array.isArray(parsed.errors)) { + fieldErrors = parsed.errors + .map((e) => + typeof e?.errorMessage === 'string' + ? typeof e.fieldName === 'string' + ? `${e.fieldName}: ${e.errorMessage}` + : e.errorMessage + : undefined + ) + .filter((v): v is string => Boolean(v)) + } + } catch { + // non-JSON body + } + + const detail = + fieldErrors.length > 0 + ? fieldErrors.join('; ') + : message || truncate(bodyText, 200) || `HTTP ${status}` + const codePrefix = errorCode ? `${errorCode}: ` : '' + let realMessage = `Zoho Desk webhook creation failed (HTTP ${status}) - ${codePrefix}${detail}` + + // Only claim the edition/permission cause when Zoho's own errorCode or message + // says so. A bare 403 can equally mean a wrong org, a missing scope, or a bad + // token, and those must not get the misleading "requires Professional" suffix. + const indicatesEditionOrPermission = + (errorCode ? ZOHO_EDITION_PERMISSION_PATTERN.test(errorCode) : false) || + ZOHO_EDITION_PERMISSION_PATTERN.test(detail) + if (indicatesEditionOrPermission) { + realMessage += + ' (Zoho Desk webhooks require a Professional edition or higher and the Desk.webhooks.CREATE scope.)' + } + + // Rate limits and provider-side 5xx are transient -> keep retryable. + if (status === 429 || status >= 500) { + return statusError(realMessage, 503) + } + // Other client errors cannot succeed on retry -> carry the 4xx status. + return statusError(realMessage, status >= 400 && status < 500 ? status : 422) +} + +export const zohoDeskHandler: WebhookProviderHandler = { + // Zoho requires a 200 within 5 seconds, so acknowledge ingress before running + // the workflow through the durable queue rather than inline. + executionMode: 'queue', + + async createSubscription({ + webhook: webhookRecord, + userId, + requestId, + }: SubscriptionContext): Promise { + const config = ((webhookRecord as Record).providerConfig ?? {}) as Record< + string, + unknown + > + const credentialId = typeof config.credentialId === 'string' ? config.credentialId : undefined + const orgId = resolveConfigOrgId(config) + const eventType = typeof config.eventType === 'string' ? config.eventType : undefined + + // Missing configuration is permanent - carry a 4xx so the deploy outbox fails + // terminally (NonRetryableDeploymentError) instead of retrying a create that + // can never succeed without the user fixing the trigger config. + if (!orgId) { + throw statusError( + 'Zoho Desk Organization ID is required to create the webhook subscription.', + 400 + ) + } + if (!eventType) { + throw statusError( + 'A Zoho Desk event type is required to create the webhook subscription.', + 400 + ) + } + + const owner = credentialId ? await getCredentialOwner(credentialId, requestId) : null + const accessToken = owner + ? await refreshAccessTokenIfNeeded(owner.accountId, owner.userId, requestId) + : null + if (!accessToken || !owner) { + throw statusError( + 'Zoho Desk account connection required. Please connect your Zoho Desk account in the trigger configuration and try again.', + 400 + ) + } + + // Already allowlist-anchored by resolveZohoDeskApiDomain -> safeZohoDeskBase. + const apiDomain = await resolveZohoDeskApiDomain(owner.accountId) + const notificationUrl = getNotificationUrl(webhookRecord) + + const filter: Record = {} + // Zoho supports `departmentIds` only on the ticket/task-family events; for + // Contact_*, Agent_*, Article_* and Task_Delete its docs say "This event does + // not support filters. Therefore, pass the value as null in the API request." + // Sending it anyway is at best a silent no-op and at worst INVALID_DATA. + if (DEPARTMENT_FILTERABLE_EVENTS.has(eventType)) { + const departmentIds = splitCsv(config.triggerDepartmentIds) + if (departmentIds.length > 0) filter.departmentIds = departmentIds + } + // `includePrevState` defaults to false, so without it Zoho never sends + // `prevState` and the trigger's declared output is permanently null for the + // update events that do support it. + if (PREV_STATE_EVENTS.has(eventType)) { + filter.includePrevState = true + } + if (eventType === 'Ticket_Update') { + const fields = splitCsv(config.fields).slice(0, 5) + if (fields.length > 0) filter.fields = fields + } + if (eventType === 'Ticket_Thread_Add') { + const direction = typeof config.direction === 'string' ? config.direction : 'both' + if (direction === 'in' || direction === 'out') filter.direction = direction + } + + const response = await fetch(`${apiDomain}/api/v1/webhooks`, { + method: 'POST', + headers: { + Authorization: `Zoho-oauthtoken ${accessToken}`, + orgId, + 'Content-Type': 'application/json', + }, + // ignoreSourceId is omitted. Zoho documents it as taking a UUID, but the + // live API rejected a well-formed v4 UUID with INVALID_DATA - the doc and + // the implementation disagree, and the field only suppresses echo of our + // own writes, which Sim does not need. + body: JSON.stringify({ + url: notificationUrl, + name: `sim-${webhookRecord.id}`.slice(0, 50), + // Zoho's own examples pass `null` (not `{}`) for an event with no + // filters - `"Contact_Add" : null`. Match the documented form. + subscriptions: { [eventType]: Object.keys(filter).length > 0 ? filter : null }, + isEnabled: true, + }), + signal: AbortSignal.timeout(15_000), + }) + + const bodyText = await response.text() + if (!response.ok) { + logger.error(`[${requestId}] Failed to create Zoho Desk webhook`, { + status: response.status, + body: truncate(bodyText, 500), + }) + throw mapZohoWebhookError(response.status, bodyText) + } + + let created: Record = {} + try { + created = JSON.parse(bodyText) + } catch { + // Zoho returns JSON on success; tolerate an empty body. + } + const idValue = created.id + const externalId = + typeof idValue === 'string' ? idValue : typeof idValue === 'number' ? String(idValue) : '' + // Never persist a subscription without its id: the id is the JWT `aud` claim + // verifyAuth checks, so an empty one would force verification to fail open. + if (!externalId) { + // Zoho reported success but gave no id: a webhook may have been created and + // is unidentifiable, so retrying risks duplicates. Fail terminally (4xx). + throw statusError('Zoho Desk webhook creation succeeded but returned no webhook id', 422) + } + + logger.info(`[${requestId}] Created Zoho Desk webhook`, { externalId }) + return { + // externalId (the JWT `aud` claim) and apiDomain are provider-managed and + // are SYSTEM_MANAGED_FIELDS, so they are excluded from the redeploy config + // diff to avoid delete/recreate churn on every deploy. + providerConfigUpdates: { externalId, apiDomain }, + } + }, + + async deleteSubscription({ + webhook: webhookRecord, + requestId, + strict, + }: DeleteSubscriptionContext): Promise { + const config = ((webhookRecord as Record).providerConfig ?? {}) as Record< + string, + unknown + > + const externalId = typeof config.externalId === 'string' ? config.externalId : undefined + const orgId = resolveConfigOrgId(config) + const credentialId = typeof config.credentialId === 'string' ? config.credentialId : undefined + + if (!externalId || !orgId) { + if (strict) throw new Error('Missing Zoho Desk webhook identifiers for deletion') + return + } + + const owner = credentialId ? await getCredentialOwner(credentialId, requestId) : null + const accessToken = owner + ? await refreshAccessTokenIfNeeded(owner.accountId, owner.userId, requestId) + : null + if (!accessToken || !owner) { + if (strict) throw new Error('Missing Zoho Desk token for webhook deletion') + return + } + + const apiDomain = + typeof config.apiDomain === 'string' && config.apiDomain + ? safeZohoDeskBase(config.apiDomain) + : await resolveZohoDeskApiDomain(owner.accountId) + + const response = await fetch(`${apiDomain}/api/v1/webhooks/${externalId}`, { + method: 'DELETE', + headers: { + Authorization: `Zoho-oauthtoken ${accessToken}`, + orgId, + }, + signal: AbortSignal.timeout(15_000), + }) + + if (!response.ok && response.status !== 404) { + logger.warn(`[${requestId}] Failed to delete Zoho Desk webhook`, { status: response.status }) + if (strict) throw new Error(`Zoho Desk webhook delete failed: ${response.status}`) + } + }, + + async verifyAuth({ + request, + requestId, + providerConfig, + }: AuthContext): Promise { + const token = request.headers.get('x-zdesk-jwt') + if (!token) { + logger.warn(`[${requestId}] Zoho Desk webhook missing X-ZDesk-JWT header`) + return new NextResponse('Unauthorized - Missing Zoho Desk JWT', { status: 401 }) + } + + // Same canonical-then-manual resolution as create/delete, so the JWT issuer + // claim is bound to the organization the subscription was actually created + // against no matter which side of the pair supplied it. + const orgId = resolveConfigOrgId(providerConfig) ?? '' + // `webhookId` was persisted on older rows; `externalId` is the canonical id. + const webhookId = + (typeof providerConfig.externalId === 'string' && providerConfig.externalId) || + (typeof providerConfig.webhookId === 'string' && providerConfig.webhookId) || + '' + // Fail closed: without both identifiers we cannot bind the JWT to this + // subscription, so accepting any org-JWKS RS256 token matching only the + // issuer would be a verification bypass. Reject instead of skipping a claim. + if (!orgId || !webhookId) { + logger.warn(`[${requestId}] Zoho Desk webhook missing orgId/webhookId; rejecting`) + return new NextResponse('Unauthorized - webhook not fully provisioned', { status: 401 }) + } + + // Prefer the apiDomain persisted on the webhook row (fast path, no DB hit on + // the 5s-deadline verification path). Only when it is absent - older rows, or + // a config that never captured it - fall back to the Desk base stored on the + // OAuth credential (`__zoho_domain__` scope marker), mirroring + // deleteSubscription, so a non-US org verifies against its own JWKS rather + // than defaulting to the US host and rejecting legitimate events. + let apiDomain = + typeof providerConfig.apiDomain === 'string' && providerConfig.apiDomain + ? safeZohoDeskBase(providerConfig.apiDomain) + : '' + if (!apiDomain) { + const credentialId = + typeof providerConfig.credentialId === 'string' ? providerConfig.credentialId : undefined + const owner = credentialId ? await getCredentialOwner(credentialId, requestId) : null + apiDomain = owner ? await resolveZohoDeskApiDomain(owner.accountId) : DEFAULT_ZOHO_DESK_BASE + } + + let deskHost: string + try { + deskHost = new URL(apiDomain).host + } catch { + deskHost = 'desk.zoho.com' + } + + try { + await jose.jwtVerify(token, getJwks(deskHost), { + algorithms: ['RS256'], + issuer: `orgId:${orgId}`, + audience: `webhookId:${webhookId}`, + }) + return null + } catch (error) { + logger.warn(`[${requestId}] Zoho Desk JWT verification failed`, { + message: toError(error).message, + }) + return new NextResponse('Unauthorized - Invalid Zoho Desk JWT', { status: 401 }) + } + }, + + async formatInput({ body, requestId }: FormatInputContext): Promise { + // Zoho Desk delivers an array of events: [{ payload, prevState, eventTime, eventType, orgId }]. + // Anything that is not the expected array shape is passed through unchanged. + if (!Array.isArray(body)) { + return { input: body } + } + // Zoho fires one event per notification (single-element array). Log rather + // than silently drop if that ever changes, so batched deliveries are visible. + if (body.length > 1) { + logger.warn( + `[${requestId}] Zoho Desk delivered ${body.length} events in one payload; processing the first only` + ) + } + const event = body[0] + if (!event || typeof event !== 'object') { + // Empty or malformed array (e.g. Zoho posts `[]`): emit the normalized + // trigger shape with null fields so downstream steps still see + // eventType/payload/etc. rather than a raw array leaking through. + return { + input: { eventType: null, eventTime: null, orgId: null, payload: null, prevState: null }, + } + } + const record = event as Record + // Comment / thread event payloads carry a raw `content` + `contentType` + // ('html' | 'plainText') pair; augment both `payload` and `prevState` with a + // derived plain-text `contentText` (HTML stripped) alongside the untouched raw + // content, so before/after comparisons see a consistent shape. Values without + // a content pair (ticket / contact events) pass through unchanged. + return { + input: { + eventType: record.eventType ?? null, + eventTime: record.eventTime ?? null, + orgId: record.orgId ?? null, + payload: record.payload != null ? withDerivedContentText(record.payload) : null, + prevState: record.prevState != null ? withDerivedContentText(record.prevState) : null, + }, + } + }, +} diff --git a/apps/sim/lib/workflows/subblocks/context.ts b/apps/sim/lib/workflows/subblocks/context.ts index 0d94267af50..b56ca0285dc 100644 --- a/apps/sim/lib/workflows/subblocks/context.ts +++ b/apps/sim/lib/workflows/subblocks/context.ts @@ -37,6 +37,7 @@ export const SELECTOR_CONTEXT_FIELDS = new Set([ 'awsRegion', 'logGroupName', 'tableId', + 'orgId', ]) /** diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts index ae8fb07e711..f39614c6987 100644 --- a/apps/sim/serializer/index.ts +++ b/apps/sim/serializer/index.ts @@ -60,7 +60,12 @@ function shouldSerializeSubBlock( if (isToolInputOnlySubBlock(subBlockConfig)) return false if (isSubBlockHidden(subBlockConfig)) return false - if (subBlockConfig.mode === 'trigger') { + // `trigger-advanced` is a trigger-mode field too - the advanced twin of a + // `trigger` selector - so it must be excluded from tool-mode serialization for + // the same reason. Without this it stays a live, validated tool param: a + // trigger's `required` manual field then blocks running an unrelated operation + // that does not even render it, and the error names a hidden field. + if (subBlockConfig.mode === 'trigger' || subBlockConfig.mode === 'trigger-advanced') { if (!isTriggerContext && !isTriggerCategory) return false } else if (isTriggerContext && !isTriggerCategory) { return false diff --git a/apps/sim/tools/generated/tool-ids.ts b/apps/sim/tools/generated/tool-ids.ts index 62074f9f876..6c6425c38ca 100644 --- a/apps/sim/tools/generated/tool-ids.ts +++ b/apps/sim/tools/generated/tool-ids.ts @@ -3,7 +3,7 @@ /** Every registered tool id, including versioned variants. */ const toolIds: string[] = JSON.parse( - '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_lock_record","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_update_candidate","athena_batch_get_query_execution","athena_create_named_query","athena_delete_named_query","athena_get_named_query","athena_get_query_execution","athena_get_query_results","athena_list_databases","athena_list_named_queries","athena_list_query_executions","athena_list_table_metadata","athena_start_query","athena_stop_query","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_webhook","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_type","calendly_get_scheduled_event","calendly_list_event_invitees","calendly_list_event_types","calendly_list_scheduled_events","calendly_list_webhooks","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_dns_record","cloudflare_create_zone","cloudflare_delete_dns_record","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_update_dns_record","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_query_sensors","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_cancel_downtime","datadog_create_downtime","datadog_create_event","datadog_create_monitor","datadog_get_monitor","datadog_list_downtimes","datadog_list_monitors","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_send_logs","datadog_submit_metrics","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","evernote_copy_note","evernote_create_note","evernote_create_notebook","evernote_create_tag","evernote_delete_note","evernote_get_note","evernote_get_notebook","evernote_list_notebooks","evernote_list_tags","evernote_search_notes","evernote_update_note","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_decompress","file_fetch","file_get","file_get_content","file_manage_sharing","file_parser","file_parser_v2","file_parser_v3","file_read","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_update_alert_rule","grafana_update_annotation","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_get_note","granola_list_folders","granola_list_notes","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_role","iam_get_user","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_list","incidentio_actions_show","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_group_member","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_users","microsoft_ad_remove_group_member","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","millionverifier_get_credits","millionverifier_verify_email","mistral_parser","mistral_parser_v2","mistral_parser_v3","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_user","okta_add_user_to_group","okta_create_group","okta_create_user","okta_deactivate_user","okta_delete_group","okta_delete_user","okta_get_group","okta_get_user","okta_list_group_members","okta_list_groups","okta_list_users","okta_remove_user_from_group","okta_reset_password","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quiver_image_to_svg","quiver_list_models","quiver_text_to_svg","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_aggregate","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_list_attachments","servicenow_read_record","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_create_channel_canvas","slack_create_conversation","slack_delete_canvas","slack_delete_message","slack_delete_scheduled_message","slack_download","slack_edit_canvas","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_message","slack_get_permalink","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_invite_to_conversation","slack_list_canvases","slack_list_channels","slack_list_members","slack_list_scheduled_messages","slack_list_users","slack_lookup_canvas_sections","slack_message","slack_message_reader","slack_open_view","slack_publish_view","slack_push_view","slack_remove_reaction","slack_rename_conversation","slack_schedule_message","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_title","slack_update_message","slack_update_view","sms_send","smtp_send_mail","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_send","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' + '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_lock_record","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_update_candidate","athena_batch_get_query_execution","athena_create_named_query","athena_delete_named_query","athena_get_named_query","athena_get_query_execution","athena_get_query_results","athena_list_databases","athena_list_named_queries","athena_list_query_executions","athena_list_table_metadata","athena_start_query","athena_stop_query","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_webhook","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_type","calendly_get_scheduled_event","calendly_list_event_invitees","calendly_list_event_types","calendly_list_scheduled_events","calendly_list_webhooks","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_dns_record","cloudflare_create_zone","cloudflare_delete_dns_record","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_update_dns_record","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_query_sensors","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_cancel_downtime","datadog_create_downtime","datadog_create_event","datadog_create_monitor","datadog_get_monitor","datadog_list_downtimes","datadog_list_monitors","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_send_logs","datadog_submit_metrics","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","evernote_copy_note","evernote_create_note","evernote_create_notebook","evernote_create_tag","evernote_delete_note","evernote_get_note","evernote_get_notebook","evernote_list_notebooks","evernote_list_tags","evernote_search_notes","evernote_update_note","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_decompress","file_fetch","file_get","file_get_content","file_manage_sharing","file_parser","file_parser_v2","file_parser_v3","file_read","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_update_alert_rule","grafana_update_annotation","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_get_note","granola_list_folders","granola_list_notes","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_role","iam_get_user","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_list","incidentio_actions_show","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_group_member","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_users","microsoft_ad_remove_group_member","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","millionverifier_get_credits","millionverifier_verify_email","mistral_parser","mistral_parser_v2","mistral_parser_v3","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_user","okta_add_user_to_group","okta_create_group","okta_create_user","okta_deactivate_user","okta_delete_group","okta_delete_user","okta_get_group","okta_get_user","okta_list_group_members","okta_list_groups","okta_list_users","okta_remove_user_from_group","okta_reset_password","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quiver_image_to_svg","quiver_list_models","quiver_text_to_svg","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_aggregate","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_list_attachments","servicenow_read_record","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_create_channel_canvas","slack_create_conversation","slack_delete_canvas","slack_delete_message","slack_delete_scheduled_message","slack_download","slack_edit_canvas","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_message","slack_get_permalink","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_invite_to_conversation","slack_list_canvases","slack_list_channels","slack_list_members","slack_list_scheduled_messages","slack_list_users","slack_lookup_canvas_sections","slack_message","slack_message_reader","slack_open_view","slack_publish_view","slack_push_view","slack_remove_reaction","slack_rename_conversation","slack_schedule_message","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_title","slack_update_message","slack_update_view","sms_send","smtp_send_mail","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_send","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoho_desk_add_comment","zoho_desk_get_attachment","zoho_desk_get_contact","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_comments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_update_ticket","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' ) export default toolIds diff --git a/apps/sim/tools/generated/tool-metadata.ts b/apps/sim/tools/generated/tool-metadata.ts index b87f67a6d14..a5c4970d060 100644 --- a/apps/sim/tools/generated/tool-metadata.ts +++ b/apps/sim/tools/generated/tool-metadata.ts @@ -3,7 +3,7 @@ /** Serializable metadata for every built-in tool, keyed by tool id. */ const toolMetadata: Record = JSON.parse( - '{"a2a_cancel_task":{"id":"a2a_cancel_task","name":"A2A Cancel Task","description":"Request cancellation of an in-progress A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to cancel"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}}},"a2a_get_agent_card":{"id":"a2a_get_agent_card","name":"A2A Get Agent Card","description":"Fetch the Agent Card (discovery document) for an external A2A agent.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}}},"a2a_get_task":{"id":"a2a_get_task","name":"A2A Get Task","description":"Retrieve the current state and result of an A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to retrieve"},"historyLength":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of history messages to include"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}}},"a2a_send_message":{"id":"a2a_send_message","name":"A2A Send Message","description":"Send a message to an external A2A agent and return its response.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"message":{"type":"string","required":true,"visibility":"user-or-llm","description":"The message text to send"},"data":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional structured JSON data to attach"},"files":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional files to attach"},"taskId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Existing task ID to continue"},"contextId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversation context ID to continue"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}}},"agentmail_create_draft":{"id":"agentmail_create_draft","name":"Create Draft","description":"Create a new email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to create the draft in"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"inReplyTo":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of message being replied to"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}}},"agentmail_create_inbox":{"id":"agentmail_create_inbox","name":"Create Inbox","description":"Create a new email inbox with AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"username":{"type":"string","required":false,"visibility":"user-or-llm","description":"Username for the inbox email address"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Domain for the inbox email address"},"displayName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Display name for the inbox"}}},"agentmail_delete_draft":{"id":"agentmail_delete_draft","name":"Delete Draft","description":"Delete an email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to delete"}}},"agentmail_delete_inbox":{"id":"agentmail_delete_inbox","name":"Delete Inbox","description":"Delete an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to delete"}}},"agentmail_delete_thread":{"id":"agentmail_delete_thread","name":"Delete Thread","description":"Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to delete"},"permanent":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Force permanent deletion instead of moving to trash"}}},"agentmail_forward_message":{"id":"agentmail_forward_message","name":"Forward Message","description":"Forward an email message to new recipients in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to forward"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional plain text to prepend"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional HTML to prepend"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}}},"agentmail_get_draft":{"id":"agentmail_get_draft","name":"Get Draft","description":"Get details of a specific email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox the draft belongs to"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to retrieve"}}},"agentmail_get_inbox":{"id":"agentmail_get_inbox","name":"Get Inbox","description":"Get details of a specific email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to retrieve"}}},"agentmail_get_message":{"id":"agentmail_get_message","name":"Get Message","description":"Get details of a specific email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to retrieve"}}},"agentmail_get_thread":{"id":"agentmail_get_thread","name":"Get Thread","description":"Get details of a specific email thread including messages in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to retrieve"}}},"agentmail_list_drafts":{"id":"agentmail_list_drafts","name":"List Drafts","description":"List email drafts in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list drafts from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of drafts to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}}},"agentmail_list_inboxes":{"id":"agentmail_list_inboxes","name":"List Inboxes","description":"List all email inboxes in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of inboxes to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}}},"agentmail_list_messages":{"id":"agentmail_list_messages","name":"List Messages","description":"List messages in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list messages from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of messages to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}}},"agentmail_list_threads":{"id":"agentmail_list_threads","name":"List Threads","description":"List email threads in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list threads from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of threads to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"},"labels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to filter threads by"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads after this ISO 8601 timestamp"}}},"agentmail_reply_message":{"id":"agentmail_reply_message","name":"Reply to Message","description":"Reply to an existing email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to reply from"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to reply to"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text reply body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML reply body"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override recipient email addresses (comma-separated)"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC email addresses (comma-separated)"},"replyAll":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reply to all recipients of the original message"}}},"agentmail_send_draft":{"id":"agentmail_send_draft","name":"Send Draft","description":"Send an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to send"}}},"agentmail_send_message":{"id":"agentmail_send_message","name":"Send Message","description":"Send an email message from an AgentMail inbox","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to send from"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email address (comma-separated for multiple)"},"subject":{"type":"string","required":true,"visibility":"user-or-llm","description":"Email subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text email body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML email body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}}},"agentmail_update_draft":{"id":"agentmail_update_draft","name":"Update Draft","description":"Update an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to update"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}}},"agentmail_update_inbox":{"id":"agentmail_update_inbox","name":"Update Inbox","description":"Update the display name of an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to update"},"displayName":{"type":"string","required":true,"visibility":"user-or-llm","description":"New display name for the inbox"}}},"agentmail_update_message":{"id":"agentmail_update_message","name":"Update Message","description":"Add or remove labels on an email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the message"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the message"}}},"agentmail_update_thread":{"id":"agentmail_update_thread","name":"Update Thread Labels","description":"Add or remove labels on an email thread in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the thread"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the thread"}}},"agentphone_create_call":{"id":"agentphone_create_call","name":"Create Outbound Call","description":"Initiate an outbound voice call from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent that will handle the call"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number to call in E.164 format (e.g. +14155551234)"},"fromNumberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent\'s first assigned number is used."},"initialGreeting":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional greeting spoken when the recipient answers"},"voice":{"type":"string","required":false,"visibility":"user-or-llm","description":"Voice ID override for this call (defaults to the agent\'s configured voice)"},"systemPrompt":{"type":"string","required":false,"visibility":"user-or-llm","description":"When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook"}}},"agentphone_create_contact":{"id":"agentphone_create_contact","name":"Create Contact","description":"Create a new contact in AgentPhone","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"phoneNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number in E.164 format (e.g. +14155551234)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact\'s full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Contact\'s email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Freeform notes stored on the contact"}}},"agentphone_create_number":{"id":"agentphone_create_number","name":"Create Phone Number","description":"Provision a new SMS- and voice-enabled phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code (e.g. US, CA). Defaults to US."},"areaCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Preferred area code (US/CA only, e.g. \\"415\\"). Best-effort — may be ignored if unavailable."},"agentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optionally attach the number to an agent immediately"}}},"agentphone_delete_contact":{"id":"agentphone_delete_contact","name":"Delete Contact","description":"Delete a contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}}},"agentphone_get_call":{"id":"agentphone_get_call","name":"Get Call","description":"Fetch a call and its full transcript","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve"}}},"agentphone_get_call_transcript":{"id":"agentphone_get_call_transcript","name":"Get Call Transcript","description":"Get the full ordered transcript for a call","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve the transcript for"}}},"agentphone_get_contact":{"id":"agentphone_get_contact","name":"Get Contact","description":"Fetch a single contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}}},"agentphone_get_conversation":{"id":"agentphone_get_conversation","name":"Get Conversation","description":"Get a conversation along with its recent messages","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"messageLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of recent messages to include (default 50, max 100)"}}},"agentphone_get_conversation_messages":{"id":"agentphone_get_conversation_messages","name":"Get Conversation Messages","description":"Get paginated messages for a conversation","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}}},"agentphone_get_number_messages":{"id":"agentphone_get_number_messages","name":"Get Phone Number Messages","description":"Fetch messages received on a specific phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}}},"agentphone_get_usage":{"id":"agentphone_get_usage","name":"Get Usage","description":"Retrieve current usage statistics for the AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"}}},"agentphone_get_usage_daily":{"id":"agentphone_get_usage_daily","name":"Get Daily Usage","description":"Get a daily breakdown of usage (messages, calls, webhooks) for the last N days","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"days":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of days to return (1-365, default 30)"}}},"agentphone_get_usage_monthly":{"id":"agentphone_get_usage_monthly","name":"Get Monthly Usage","description":"Get monthly usage aggregation (messages, calls, webhooks) for the last N months","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"months":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of months to return (1-24, default 6)"}}},"agentphone_list_calls":{"id":"agentphone_list_calls","name":"List Calls","description":"List voice calls for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"},"status":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by status (completed, in-progress, failed)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by direction (inbound, outbound)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by call type (pstn, web)"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search by phone number (matches fromNumber or toNumber)"}}},"agentphone_list_contacts":{"id":"agentphone_list_contacts","name":"List Contacts","description":"List contacts for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by name or phone number (case-insensitive contains)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 50, max 200)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}}},"agentphone_list_conversations":{"id":"agentphone_list_conversations","name":"List Conversations","description":"List conversations (message threads) for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}}},"agentphone_list_numbers":{"id":"agentphone_list_numbers","name":"List Phone Numbers","description":"List all phone numbers provisioned for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}}},"agentphone_react_to_message":{"id":"agentphone_react_to_message","name":"React to Message","description":"Send an iMessage tapback reaction to a message (iMessage only)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to react to"},"reaction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Reaction type: love, like, dislike, laugh, emphasize, or question"}}},"agentphone_release_number":{"id":"agentphone_release_number","name":"Release Phone Number","description":"Release (delete) a phone number. This action is irreversible.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number to release"}}},"agentphone_send_message":{"id":"agentphone_send_message","name":"Send Message","description":"Send an outbound SMS or iMessage from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent sending the message"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient phone number in E.164 format (e.g. +14155551234)"},"body":{"type":"string","required":true,"visibility":"user-or-llm","description":"Message text to send"},"mediaUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional URL of an image, video, or file to attach"},"numberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to send from. If omitted, the agent\'s first assigned number is used."}}},"agentphone_update_contact":{"id":"agentphone_update_contact","name":"Update Contact","description":"Update a contact\'s fields","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"New phone number in E.164 format"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New contact name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"New email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"New freeform notes"}}},"agentphone_update_conversation":{"id":"agentphone_update_conversation","name":"Update Conversation","description":"Update conversation metadata (stored state). Pass null to clear existing metadata.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"metadata":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom key-value metadata to store on the conversation. Pass null to clear existing metadata."}}},"agiloft_attach_file":{"id":"agiloft_attach_file","name":"Agiloft Attach File","description":"Attach a file to a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to attach the file to"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"file":{"type":"file","required":false,"visibility":"user-or-llm","description":"File to attach"},"fileName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name to assign to the file (defaults to original file name)"}}},"agiloft_attachment_info":{"id":"agiloft_attachment_info","name":"Agiloft Attachment Info","description":"Get information about file attachments on a record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to check attachments on"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field to inspect"}}},"agiloft_create_record":{"id":"agiloft_create_record","name":"Agiloft Create Record","description":"Create a new record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record field values as a JSON object (e.g., {\\"first_name\\": \\"John\\", \\"status\\": \\"Active\\"})"}}},"agiloft_delete_record":{"id":"agiloft_delete_record","name":"Agiloft Delete Record","description":"Delete a record from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to delete"}}},"agiloft_get_choice_line_id":{"id":"agiloft_get_choice_line_id","name":"Agiloft Get Choice Line ID","description":"Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"case\\", \\"contracts\\")"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice field name (e.g., \\"priority\\", \\"status\\")"},"value":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice display value to resolve (e.g., \\"High\\", \\"Active\\")"}}},"agiloft_lock_record":{"id":"agiloft_lock_record","name":"Agiloft Lock Record","description":"Lock, unlock, or check the lock status of an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to lock, unlock, or check"},"lockAction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Action to perform: \\"lock\\", \\"unlock\\", or \\"check\\""}}},"agiloft_read_record":{"id":"agiloft_read_record","name":"Agiloft Read Record","description":"Read a record by ID from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to read"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the response"}}},"agiloft_remove_attachment":{"id":"agiloft_remove_attachment","name":"Agiloft Remove Attachment","description":"Remove an attached file from a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file to remove (starting from 0)"}}},"agiloft_retrieve_attachment":{"id":"agiloft_retrieve_attachment","name":"Agiloft Retrieve Attachment","description":"Download an attached file from an Agiloft record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file in the field (starting from 0)"}}},"agiloft_saved_search":{"id":"agiloft_saved_search","name":"Agiloft Saved Search","description":"List saved searches defined for an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name to list saved searches for (e.g., \\"contracts\\")"}}},"agiloft_search_records":{"id":"agiloft_search_records","name":"Agiloft Search Records","description":"Search for records in an Agiloft table using a query.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name to search in (e.g., \\"contracts\\", \\"contacts.employees\\")"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"Search query using Agiloft query syntax (e.g., \\"status=\'Active\'\\" or \\"company_name~=\'Acme\'\\")"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the results"},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number for paginated results (starting from 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return per page"}}},"agiloft_select_records":{"id":"agiloft_select_records","name":"Agiloft Select Records","description":"Select record IDs matching a SQL WHERE clause from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"where":{"type":"string","required":true,"visibility":"user-or-llm","description":"SQL WHERE clause using database column names (e.g., \\"summary like \'%new%\'\\" or \\"assigned_person=\'John Doe\'\\")"}}},"agiloft_update_record":{"id":"agiloft_update_record","name":"Agiloft Update Record","description":"Update an existing record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to update"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Updated field values as a JSON object (e.g., {\\"status\\": \\"Active\\", \\"priority\\": \\"High\\"})"}}},"ahrefs_anchors":{"id":"ahrefs_anchors","name":"Ahrefs Anchors","description":"Get the anchor text distribution for a target domain or URL\'s backlinks, showing how many links and referring domains use each anchor text.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_backlinks":{"id":"ahrefs_backlinks","name":"Ahrefs Backlinks","description":"Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live backlinks), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_backlinks_stats":{"id":"ahrefs_backlinks_stats","name":"Ahrefs Backlinks Stats","description":"Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_batch_analysis":{"id":"ahrefs_batch_analysis","name":"Ahrefs Batch Analysis","description":"Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traffic, and more) for multiple domains or URLs in a single request. Useful for comparing many competitors at once.","version":"1.0.0","params":{"targets":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated list of domains or URLs to analyze. Example: \\"example.com,competitor.com\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode applied to every target: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"protocol":{"type":"string","required":false,"visibility":"user-or-llm","description":"Protocol applied to every target: \\"both\\" (default), \\"http\\", or \\"https\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_broken_backlinks":{"id":"ahrefs_broken_backlinks","name":"Ahrefs Broken Backlinks","description":"Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_domain_rating":{"id":"ahrefs_domain_rating","name":"Ahrefs Domain Rating","description":"Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website\'s backlink profile on a scale from 0 to 100.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze (e.g., example.com)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date for historical data in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_domain_rating_history":{"id":"ahrefs_domain_rating_history","name":"Ahrefs Domain Rating History","description":"Get the historical Domain Rating (DR) trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_keyword_overview":{"id":"ahrefs_keyword_overview","name":"Ahrefs Keyword Overview","description":"Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The keyword to analyze"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_keywords_history":{"id":"ahrefs_keywords_history","name":"Ahrefs Keywords History","description":"Get the historical organic keyword ranking distribution for a target domain or URL over a date range: how many keywords rank in each position bucket at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_metrics":{"id":"ahrefs_metrics","name":"Ahrefs Metrics","description":"Get a one-call organic and paid search overview for a target domain or URL: organic traffic, organic keywords, paid traffic, paid keywords, and estimated traffic cost.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_metrics_history":{"id":"ahrefs_metrics_history","name":"Ahrefs Metrics History","description":"Get the historical organic and paid traffic trend for a target domain or URL over a date range: organic traffic/cost and paid traffic/cost at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_organic_competitors":{"id":"ahrefs_organic_competitors","name":"Ahrefs Organic Competitors","description":"Get domains that compete with a target domain or URL for the same organic keywords, ranked by keyword overlap.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_organic_keywords":{"id":"ahrefs_organic_keywords","name":"Ahrefs Organic Keywords","description":"Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_paid_pages":{"id":"ahrefs_paid_pages","name":"Ahrefs Paid Pages","description":"Get a target domain\'s pages that receive paid search traffic, sorted by estimated paid traffic. Returns page URLs with their paid traffic, keyword counts, and estimated spend.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_rank_tracker_competitors_overview":{"id":"ahrefs_rank_tracker_competitors_overview","name":"Ahrefs Rank Tracker Competitors Overview","description":"Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker project: each tracked keyword\'s volume and difficulty alongside every competitor\'s position, traffic, and traffic value. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_rank_tracker_competitors_stats":{"id":"ahrefs_rank_tracker_competitors_stats","name":"Ahrefs Rank Tracker Competitors Stats","description":"Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competitor\'s traffic, traffic value, average position, and share of voice across all tracked keywords. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report metrics for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_rank_tracker_overview":{"id":"ahrefs_rank_tracker_overview","name":"Ahrefs Rank Tracker Overview","description":"Get ranking overview metrics for the keywords tracked in an Ahrefs Rank Tracker project: position, search volume, keyword difficulty, and estimated traffic. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_rank_tracker_serp_overview":{"id":"ahrefs_rank_tracker_serp_overview","name":"Ahrefs Rank Tracker SERP Overview","description":"Get the full SERP (search engine results page) for a keyword tracked in an Ahrefs Rank Tracker project, including every ranking URL with its position, title, and authority metrics. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The tracked keyword to retrieve SERP data for"},"country":{"type":"string","required":true,"visibility":"user-or-llm","description":"Country code for the tracked keyword. Example: \\"us\\", \\"gb\\", \\"de\\""},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"topPositions":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of top organic positions to return (defaults to all available)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Timestamp to return the last available SERP Overview at, in YYYY-MM-DDThh:mm:ss format"},"locationId":{"type":"number","required":false,"visibility":"user-or-llm","description":"Location ID of the tracked keyword, if tracked at a specific location"},"languageCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code of the tracked keyword"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_refdomains_history":{"id":"ahrefs_refdomains_history","name":"Ahrefs Referring Domains History","description":"Get the historical referring domains trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_referring_domains":{"id":"ahrefs_referring_domains","name":"Ahrefs Referring Domains","description":"Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost domains), or \\"since:YYYY-MM-DD\\" (domains found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_related_terms":{"id":"ahrefs_related_terms","name":"Ahrefs Related Terms","description":"Get keyword ideas related to a seed keyword: terms the same top-ranking pages also rank for (\\"also rank for\\") or also discuss (\\"also talk about\\"), with volume, difficulty, and CPC.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The seed keyword to find related terms for"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"terms":{"type":"string","required":false,"visibility":"user-or-llm","description":"Type of related keywords to return: \\"also_rank_for\\", \\"also_talk_about\\", or \\"all\\" (default: \\"all\\")"},"viewFor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Whether to derive related terms from the top 10 or top 100 ranking pages (default: \\"top_10\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_site_audit_page_explorer":{"id":"ahrefs_site_audit_page_explorer","name":"Ahrefs Site Audit Page Explorer","description":"Get crawled pages from an Ahrefs Site Audit project with health and SEO metrics: HTTP status, title, link counts, backlinks, indexability, and traffic. Optionally filter to pages affected by a specific issue.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Site Audit project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Crawl date in YYYY-MM-DDThh:mm:ss format (defaults to the most recent crawl)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip, for pagination"},"issueId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Only return pages affected by this issue ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"ahrefs_top_pages":{"id":"ahrefs_top_pages","name":"Ahrefs Top Pages","description":"Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}}},"airtable_create_records":{"id":"airtable_create_records","name":"Airtable Create Records","description":"Write new records to an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to create, each with a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_delete_records":{"id":"airtable_delete_records","name":"Airtable Delete Records","description":"Delete one or more records from an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordIds":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of record IDs to delete (each starts with \\"rec\\", e.g., [\\"recXXXXXXXXXXXXXX\\"]). Pass a single-element array to delete one record."}},"oauth":{"required":true,"provider":"airtable"}},"airtable_get_base_schema":{"id":"airtable_get_base_schema","name":"Airtable Get Base Schema","description":"Get the schema of all tables, fields, and views in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_get_record":{"id":"airtable_get_record","name":"Airtable Get Record","description":"Retrieve a single record from an Airtable table by its ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to retrieve (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_list_bases":{"id":"airtable_list_bases","name":"Airtable List Bases","description":"List all bases the authenticated user has access to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination offset for retrieving additional bases"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_list_records":{"id":"airtable_list_records","name":"Airtable List Records","description":"Read records from an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"maxRecords":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return (default: all records)"},"filterFormula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Formula to filter records (e.g., \\"({Field Name} = \'Value\')\\")"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_list_tables":{"id":"airtable_list_tables","name":"Airtable List Tables","description":"List all tables and their schema in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_update_multiple_records":{"id":"airtable_update_multiple_records","name":"Airtable Update Multiple Records","description":"Update multiple existing records in an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to update, each with an `id` and a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_update_record":{"id":"airtable_update_record","name":"Airtable Update Record","description":"Update an existing record in an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to update (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"},"fields":{"type":"json","required":true,"visibility":"user-or-llm","description":"An object containing the field names and their new values"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"}},"airtable_upsert_records":{"id":"airtable_upsert_records","name":"Airtable Upsert Records","description":"Update existing records or create new ones in an Airtable table, matching on the specified merge fields","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to upsert, each with a `fields` object"},"fieldsToMergeOn":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of field names used to match existing records (max 3). A record is updated when all merge fields match, otherwise it is created. Example: [\\"Name\\"]"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"}},"airweave_search":{"id":"airweave_search","name":"Airweave Search","description":"Search your synced data collections using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Airweave API Key for authentication"},"collectionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The readable ID of the collection to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query text"},"limit":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 100)"},"retrievalStrategy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retrieval strategy: hybrid (default), neural, or keyword"},"expandQuery":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate query variations to improve recall"},"rerank":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reorder results for improved relevance using LLM"},"generateAnswer":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate a natural-language answer to the query"}}},"algolia_add_record":{"id":"algolia_add_record","name":"Algolia Add Record","description":"Add or replace a record in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":false,"visibility":"user-or-llm","description":"Object ID for the record (auto-generated if not provided)"},"record":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object representing the record to add"}}},"algolia_batch_operations":{"id":"algolia_batch_operations","name":"Algolia Batch Operations","description":"Perform batch add, update, partial update, or delete operations on records in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of batch operations. Each item has \\"action\\" (addObject, updateObject, partialUpdateObject, partialUpdateObjectNoCreate, deleteObject, delete, clear) and \\"body\\" (the record data; must include objectID for update/delete; use an empty object {} for the index-level delete/clear actions)"}}},"algolia_browse_records":{"id":"algolia_browse_records","name":"Algolia Browse Records","description":"Browse and iterate over all records in an Algolia index using cursor pagination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key (must have browse ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to browse"},"query":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search query to filter browsed records"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string to narrow down results"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 1000, max: 1000)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous browse response for pagination"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}}},"algolia_clear_records":{"id":"algolia_clear_records","name":"Algolia Clear Records","description":"Clear all records from an Algolia index while keeping settings, synonyms, and rules","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to clear"}}},"algolia_copy_move_index":{"id":"algolia_copy_move_index","name":"Algolia Copy/Move Index","description":"Copy or move an Algolia index to a new destination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the source index"},"operation":{"type":"string","required":true,"visibility":"user-or-llm","description":"Operation to perform: \\"copy\\" or \\"move\\""},"destination":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the destination index"},"scope":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of scopes to copy (only for \\"copy\\" operation): [\\"settings\\", \\"synonyms\\", \\"rules\\"]. Omit to copy everything including records."}}},"algolia_delete_by_filter":{"id":"algolia_delete_by_filter","name":"Algolia Delete By Filter","description":"Delete all records matching a filter from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter expression to match records for deletion (e.g., \\"category:outdated\\")"},"facetFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of facet filters (e.g., [\\"brand:Acme\\"])"},"numericFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of numeric filters (e.g., [\\"price > 100\\"])"},"tagFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of tag filters using the _tags attribute (e.g., [\\"published\\"])"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search filter (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search filter"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search filter"}}},"algolia_delete_index":{"id":"algolia_delete_index","name":"Algolia Delete Index","description":"Delete an entire Algolia index and all its records","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to delete"}}},"algolia_delete_record":{"id":"algolia_delete_record","name":"Algolia Delete Record","description":"Delete a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to delete"}}},"algolia_get_record":{"id":"algolia_get_record","name":"Algolia Get Record","description":"Get a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to retrieve"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"}}},"algolia_get_records":{"id":"algolia_get_records","name":"Algolia Get Records","description":"Retrieve multiple records by objectID from one or more Algolia indices","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Default index name for all requests"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of objects specifying records to retrieve. Each must have \\"objectID\\" and optionally \\"indexName\\" and \\"attributesToRetrieve\\"."}}},"algolia_get_settings":{"id":"algolia_get_settings","name":"Algolia Get Settings","description":"Retrieve the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"}}},"algolia_get_task_status":{"id":"algolia_get_task_status","name":"Algolia Get Task Status","description":"Check whether an Algolia indexing task has finished publishing","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index the task ran against"},"taskID":{"type":"number","required":true,"visibility":"user-or-llm","description":"The taskID returned by a previous write operation"}}},"algolia_list_indices":{"id":"algolia_list_indices","name":"Algolia List Indices","description":"List all indices in an Algolia application","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for paginating indices (default: not paginated)"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of indices per page (default: 100)"}}},"algolia_partial_update_record":{"id":"algolia_partial_update_record","name":"Algolia Partial Update Record","description":"Partially update a record in an Algolia index without replacing it entirely","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to update"},"attributes":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with attributes to update. Supports built-in operations like {\\"stock\\": {\\"_operation\\": \\"Decrement\\", \\"value\\": 1}}"},"createIfNotExists":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to create the record if it does not exist (default: true)"}}},"algolia_search":{"id":"algolia_search","name":"Algolia Search","description":"Search an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"Search query text"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 20)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number to retrieve (default: 0)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string (e.g., \\"category:electronics AND price < 100\\")"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"facets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of facet attribute names to retrieve counts for (use \\"*\\" for all)"},"getRankingInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to include detailed ranking information in each hit"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}}},"algolia_update_settings":{"id":"algolia_update_settings","name":"Algolia Update Settings","description":"Update the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have editSettings ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"settings":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with settings to update (e.g., {\\"searchableAttributes\\": [\\"name\\", \\"description\\"], \\"customRanking\\": [\\"desc(popularity)\\"]})"},"forwardToReplicas":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to apply changes to replica indices (default: false)"}}},"amplitude_event_segmentation":{"id":"amplitude_event_segmentation","name":"Amplitude Event Segmentation","description":"Query event analytics data with segmentation. Get event counts, uniques, averages, and more.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Event type name to analyze"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: uniques, totals, pct_dau, average, histogram, sums, value_avg, or formula (default: uniques)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (prefix custom user properties with \\"gp:\\")"},"groupBy2":{"type":"string","required":false,"visibility":"user-or-llm","description":"Second property name to group by (prefix custom user properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (max 1000)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON array of filter objects applied to the event, e.g. [{\\"subprop_type\\":\\"event\\",\\"subprop_key\\":\\"city\\",\\"subprop_op\\":\\"is\\",\\"subprop_value\\":[\\"San Francisco\\"]}]"},"formula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when metric is \\"formula\\", e.g. \\"UNIQUES(A)/UNIQUES(B)\\""},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_funnels":{"id":"amplitude_funnels","name":"Amplitude Funnels","description":"Analyze conversion rates and drop-off between a sequence of events.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"events":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON array of event objects, one per funnel step in order, e.g. [{\\"event_type\\":\\"signup\\"},{\\"event_type\\":\\"purchase\\"}]"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Funnel ordering: \\"ordered\\", \\"unordered\\", or \\"sequential\\" (default: ordered)"},"userType":{"type":"string","required":false,"visibility":"user-or-llm","description":"User type: \\"new\\" or \\"active\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: -300000 (real-time), -3600000 (hourly), 1 (daily), 7 (weekly), or 30 (monthly)"},"conversionWindowSeconds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversion window in seconds (default: 2592000, i.e. 30 days)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (default: 100, max: 1000)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_get_active_users":{"id":"amplitude_get_active_users","name":"Amplitude Get Active Users","description":"Get active or new user counts over a date range from the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: \\"active\\" or \\"new\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_get_revenue":{"id":"amplitude_get_revenue","name":"Amplitude Get Revenue","description":"Get revenue LTV data including ARPU, ARPPU, total revenue, and paying user counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric: 0 (ARPU), 1 (ARPPU), 2 (Total Revenue), 3 (Paying Users)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (limit: one)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_group_identify":{"id":"amplitude_group_identify","name":"Amplitude Group Identify","description":"Set group-level properties in Amplitude. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"groupType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Group classification (e.g., \\"company\\", \\"org_id\\")"},"groupValue":{"type":"string","required":true,"visibility":"user-or-llm","description":"Specific group identifier (e.g., \\"Acme Corp\\")"},"groupProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of group properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_identify_user":{"id":"amplitude_identify_user","name":"Amplitude Identify User","description":"Set user properties in Amplitude using the Identify API. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"userProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of user properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_list_events":{"id":"amplitude_list_events","name":"Amplitude List Events","description":"List all event types in the Amplitude project with their weekly totals and unique counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_realtime_active_users":{"id":"amplitude_realtime_active_users","name":"Amplitude Real-time Active Users","description":"Get real-time active user counts at 5-minute granularity for the last 2 days.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_retention":{"id":"amplitude_retention","name":"Amplitude Retention","description":"Measure how many users return to perform an action after a starting action.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"startEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON starting event object, e.g. {\\"event_type\\":\\"_new\\"} or {\\"event_type\\":\\"_active\\"}"},"returnEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON returning event object, e.g. {\\"event_type\\":\\"_all\\"} or {\\"event_type\\":\\"_active\\"}"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"retentionMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retention type: \\"bracket\\", \\"rolling\\", or \\"n-day\\" (default: n-day)"},"retentionBrackets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when Retention Mode is \\"bracket\\". Day ranges, e.g. [[0,4]]"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_send_event":{"id":"amplitude_send_event","name":"Amplitude Send Event","description":"Track an event in Amplitude using the HTTP V2 API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the event (e.g., \\"page_view\\", \\"purchase\\")"},"eventProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of custom event properties"},"userProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of user properties to set (supports $set, $setOnce, $add, $append, $unset)"},"time":{"type":"string","required":false,"visibility":"user-or-llm","description":"Event timestamp in milliseconds since epoch"},"sessionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Session start time in milliseconds since epoch"},"insertId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Unique ID for deduplication (within 7-day window)"},"appVersion":{"type":"string","required":false,"visibility":"user-or-llm","description":"Application version string"},"platform":{"type":"string","required":false,"visibility":"user-or-llm","description":"Platform (e.g., \\"Web\\", \\"iOS\\", \\"Android\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code"},"language":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code (e.g., \\"en\\")"},"ip":{"type":"string","required":false,"visibility":"user-or-llm","description":"IP address for geo-location"},"price":{"type":"string","required":false,"visibility":"user-or-llm","description":"Price of the item purchased"},"quantity":{"type":"string","required":false,"visibility":"user-or-llm","description":"Quantity of items purchased"},"revenue":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue amount"},"productId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Product identifier"},"revenueType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue type (e.g., \\"purchase\\", \\"refund\\")"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_user_activity":{"id":"amplitude_user_activity","name":"Amplitude User Activity","description":"Get the event stream for a specific user by their Amplitude ID.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"amplitudeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Amplitude internal user ID"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Offset for pagination (default 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of events to return (default 1000, max 1000)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort direction: \\"latest\\" or \\"earliest\\" (default: latest)"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"amplitude_user_profile":{"id":"amplitude_user_profile","name":"Amplitude User Profile","description":"Get a user profile including properties, cohort memberships, and computed properties. Not available for EU data-residency projects.","version":"1.0.0","params":{"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"External user ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"getAmpProps":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include Amplitude user properties (true/false, default: false)"},"getCohortIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include cohort IDs the user belongs to (true/false, default: false)"},"getComputations":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include computed user properties (true/false, default: false)"}}},"amplitude_user_search":{"id":"amplitude_user_search","name":"Amplitude User Search","description":"Search for a user by User ID, Device ID, or Amplitude ID using the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"user":{"type":"string","required":true,"visibility":"user-or-llm","description":"User ID, Device ID, or Amplitude ID to search for"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}}},"apify_get_dataset_items":{"id":"apify_get_dataset_items","name":"APIFY Get Dataset Items","description":"Retrieve items stored in an APIFY dataset","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"datasetId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Dataset ID to read items from. Example: \\"9RnD3Pql2vGZkc5H5\\""},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max items to return (1-250000). Default: all items. Example: 500"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to skip at the start. Default: 0"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of fields to include. Example: \\"title,url,price\\""}}},"apify_get_run":{"id":"apify_get_run","name":"APIFY Get Run","description":"Get the status and details of an APIFY actor run","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"runId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor run ID to fetch. Example: \\"HG7ML7M8z78YcAPEB\\""}}},"apify_run_actor_async":{"id":"apify_run_actor_async","name":"APIFY Run Actor (Async)","description":"Run an APIFY actor asynchronously with polling for long-running tasks","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: {\\"startUrls\\": [{\\"url\\": \\"https://example.com\\"}], \\"maxPages\\": 10}"},"waitForFinish":{"type":"number","required":false,"visibility":"user-or-llm","description":"Initial wait time in seconds (0-60) before polling starts. Example: 30"},"itemLimit":{"type":"number","required":false,"default":100,"visibility":"user-or-llm","description":"Max dataset items to fetch (1-250000). Default: 100. Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}}},"apify_run_actor_sync":{"id":"apify_run_actor_sync","name":"APIFY Run Actor (Sync)","description":"Run an APIFY actor synchronously and get results (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: {\\"startUrls\\": [{\\"url\\": \\"https://example.com\\"}], \\"maxPages\\": 10}"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}}},"apify_run_task":{"id":"apify_run_task","name":"APIFY Run Task","description":"Run a saved APIFY actor task synchronously and get dataset items (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task ID or username/task-name. Examples: \\"janedoe/my-task\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON string that overrides the task\'s saved input. Example: {\\"startUrls\\": [{\\"url\\": \\"https://example.com\\"}]}"},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max dataset items to return (1-250000). Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the run (128-32768). Example: 1024 for 1GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the run. Example: 300 for 5 minutes"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\""}}},"apollo_account_bulk_create":{"id":"apollo_account_bulk_create","name":"Apollo Bulk Create Accounts","description":"Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"accounts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of accounts to create (max 100). Each account should include a name, and may optionally include domain, phone, phone_status_cd, raw_address, owner_id, linkedin_url, facebook_url, twitter_url, salesforce_id, and hubspot_id."},"append_label_names":{"type":"array","required":false,"visibility":"user-only","description":"Array of label names to add to ALL accounts in this request"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, performs aggressive deduplication by domain, organization_id, and name (defaults to false)"}}},"apollo_account_bulk_update":{"id":"apollo_account_bulk_update","name":"Apollo Bulk Update Accounts","description":"Update up to 1000 existing accounts at once in your Apollo database (higher limit than contacts!). Each account must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"account_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of account IDs to update with the same values (max 1000). Use with name/owner_id for uniform updates. Use either this OR account_attributes."},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this name to all accounts"},"owner_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this owner to all accounts"},"account_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this account stage to all accounts"},"account_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of account objects with individual updates (each must include id). Example: [{\\"id\\": \\"acc1\\", \\"name\\": \\"Acme\\", \\"owner_id\\": \\"u1\\", \\"account_stage_id\\": \\"s1\\", \\"typed_custom_fields\\": {\\"field_id\\": \\"value\\"}}]"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes."}}},"apollo_account_create":{"id":"apollo_account_create","name":"Apollo Create Account","description":"Create a new account (company) in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain without www. prefix (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the account"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}}},"apollo_account_search":{"id":"apollo_account_search","name":"Apollo Search Accounts","description":"Search your team\'s accounts in Apollo. Display limit: 50,000 records (100 records per page, 500 pages max). Use filters to narrow results. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter accounts by organization name (partial-match search)"},"account_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account stage IDs"},"account_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account label IDs"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"account_last_activity_date\\", \\"account_created_at\\", or \\"account_updated_at\\""},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Sort ascending when true. Defaults to descending."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"apollo_account_update":{"id":"apollo_account_update","name":"Apollo Update Account","description":"Update an existing account in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the account to update (e.g., \\"acc_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company phone number"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}}},"apollo_contact_bulk_create":{"id":"apollo_contact_bulk_create","name":"Apollo Bulk Create Contacts","description":"Create up to 100 contacts at once in your Apollo database. Supports deduplication to prevent creating duplicate contacts. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contacts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contacts to create (max 100). Each contact may include first_name, last_name, email, title, organization_name, account_id, owner_id, contact_stage_id, linkedin_url, phone (single string) or phone_numbers (array of {raw_number, position}), contact_emails, typed_custom_fields, and CRM IDs (salesforce_contact_id, hubspot_id, team_id) for cross-system matching"},"append_label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Label names to add to all contacts in this request (e.g., [\\"Hot Lead\\"])"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"Enable deduplication to prevent creating duplicate contacts. When true, existing contacts are returned without modification"}}},"apollo_contact_bulk_update":{"id":"apollo_contact_bulk_update","name":"Apollo Bulk Update Contacts","description":"Update up to 100 existing contacts at once in your Apollo database. Each contact must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to update. Must be paired with an object-form contact_attributes specifying the fields to apply uniformly to all listed contacts."},"contact_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Required. Either an array of per-contact updates (each with id) — used standalone — or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"Force asynchronous processing. Automatically enabled for >100 contacts"}}},"apollo_contact_create":{"id":"apollo_contact_create","name":"Apollo Create Contact","description":"Create a new contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the contact"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID to associate with (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for POST /contacts)"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, Apollo deduplicates against existing contacts"}}},"apollo_contact_search":{"id":"apollo_contact_search","name":"Apollo Search Contacts","description":"Search your team\'s contacts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"contact_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by contact stage IDs"},"contact_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by Apollo label IDs (lists)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-only","description":"Sort field: contact_last_activity_date, contact_email_last_opened_at, contact_email_last_clicked_at, contact_created_at, or contact_updated_at"},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, sort ascending. Must be used together with sort_by_field"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"apollo_contact_update":{"id":"apollo_contact_update","name":"Apollo Update Contact","description":"Update an existing contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"contact_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the contact to update (e.g., \\"con_abc123\\")"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for PATCH /contacts/{id})"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"}}},"apollo_email_accounts":{"id":"apollo_email_accounts","name":"Apollo Get Email Accounts","description":"Get list of team\'s linked email accounts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"}}},"apollo_opportunity_create":{"id":"apollo_opportunity_create","name":"Apollo Create Opportunity","description":"Create a new deal for an account in your Apollo database (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of the account this opportunity belongs to (e.g., \\"acc_abc123\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}}},"apollo_opportunity_get":{"id":"apollo_opportunity_get","name":"Apollo Get Opportunity","description":"Retrieve complete details of a specific deal/opportunity by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to retrieve (e.g., \\"opp_abc123\\")"}}},"apollo_opportunity_search":{"id":"apollo_opportunity_search","name":"Apollo Search Opportunities","description":"Search and list all deals/opportunities in your team\'s Apollo account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"amount\\", \\"is_closed\\", or \\"is_won\\""},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"apollo_opportunity_update":{"id":"apollo_opportunity_update","name":"Apollo Update Opportunity","description":"Update an existing deal/opportunity in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to update (e.g., \\"opp_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}}},"apollo_organization_bulk_enrich":{"id":"apollo_organization_bulk_enrich","name":"Apollo Bulk Organization Enrichment","description":"Enrich data for up to 10 organizations at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domains":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of company domains to enrich (max 10, no www. or @, e.g., [\\"apollo.io\\", \\"stripe.com\\"])"}}},"apollo_organization_enrich":{"id":"apollo_organization_enrich","name":"Apollo Organization Enrichment","description":"Enrich data for a single organization using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domain":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"}}},"apollo_organization_search":{"id":"apollo_organization_search","name":"Apollo Organization Search","description":"Search Apollo\'s database for companies using filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company HQ locations (cities, US states, or countries)"},"organization_not_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Exclude companies whose HQ is in these locations"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges as \\"min,max\\" strings (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"q_organization_keyword_tags":{"type":"array","required":false,"visibility":"user-or-llm","description":"Industry or keyword tags"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Organization name to search for (e.g., \\"Acme\\", \\"TechCorp\\")"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to include (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Domain names to filter by (no www. or @, up to 1,000)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"apollo_people_bulk_enrich":{"id":"apollo_people_bulk_enrich","name":"Apollo Bulk People Enrichment","description":"Enrich data for up to 10 people at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"people":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of people to enrich (max 10)"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}}},"apollo_people_enrich":{"id":"apollo_people_enrich","name":"Apollo People Enrichment","description":"Enrich data for a single person using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the person"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the person"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Full name of the person (alternative to first_name/last_name)"},"id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the person"},"hashed_email":{"type":"string","required":false,"visibility":"user-or-llm","description":"MD5 or SHA-256 hashed email"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the person"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name where the person works"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"},"linkedin_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}}},"apollo_people_search":{"id":"apollo_people_search","name":"Apollo People Search","description":"Search Apollo\'s database for people using demographic filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"person_titles":{"type":"array","required":false,"visibility":"user-or-llm","description":"Job titles to search for (e.g., [\\"CEO\\", \\"VP of Sales\\"])"},"include_similar_titles":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to return people with job titles similar to person_titles"},"person_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Locations to search in (e.g., [\\"San Francisco, CA\\", \\"New York, NY\\"])"},"person_seniorities":{"type":"array","required":false,"visibility":"user-or-llm","description":"Seniority levels (one of: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern)"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to filter by (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"organization_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company names to search within (legacy filter)"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Headquarters locations of the people\'s current employer (e.g., [\'texas\', \'tokyo\', \'spain\'])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employer domain names (e.g., [\\"apollo.io\\", \\"microsoft.com\\"]) — up to 1,000, no www. or @"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges for the person\'s current employer. Each entry is \\"min,max\\" (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"contact_email_status":{"type":"array","required":false,"visibility":"user-or-llm","description":"Email statuses to filter by: \\"verified\\", \\"unverified\\", \\"likely to engage\\", \\"unavailable\\""},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination, default 1 (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, default 25, max 100 (e.g., 25, 50, 100)"}}},"apollo_sequence_add_contacts":{"id":"apollo_sequence_add_contacts","name":"Apollo Add Contacts to Sequence","description":"Add contacts to an Apollo sequence","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sequence_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the sequence to add contacts to (e.g., \\"seq_abc123\\")"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to add to the sequence (e.g., [\\"con_abc123\\", \\"con_def456\\"]). Either contact_ids or label_names must be provided."},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of label names to identify contacts to add to the sequence. Either contact_ids or label_names must be provided."},"send_email_from_email_account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the email account to send from. Use the Get Email Accounts operation to look this up."},"send_email_from_email_address":{"type":"string","required":false,"visibility":"user-only","description":"Specific email address to send from within the email account."},"sequence_no_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they have no email address"},"sequence_unverified_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts with unverified email addresses"},"sequence_job_change":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who recently changed jobs"},"sequence_active_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts active in other campaigns"},"sequence_finished_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who finished other campaigns"},"sequence_same_company_in_same_campaign":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if others from the same company are in the sequence"},"contacts_without_ownership_permission":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts without ownership permission"},"add_if_in_queue":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they are in the queue"},"contact_verification_skipped":{"type":"boolean","required":false,"visibility":"user-only","description":"Skip contact verification when adding"},"user_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the user performing the action"},"status":{"type":"string","required":false,"visibility":"user-only","description":"Initial status for added contacts: \\"active\\" or \\"paused\\""},"auto_unpause_at":{"type":"string","required":false,"visibility":"user-only","description":"ISO 8601 datetime to automatically unpause contacts"}}},"apollo_sequence_search":{"id":"apollo_sequence_search","name":"Apollo Search Sequences","description":"Search for sequences/campaigns in your team\'s Apollo account (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search sequences by name (e.g., \\"Outbound Q1\\", \\"Follow-up\\")"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"apollo_task_create":{"id":"apollo_task_create","name":"Apollo Create Task","description":"Create one or more tasks in Apollo (one task per contact_id, master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"user_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the Apollo user the task is assigned to"},"contact_ids":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contact IDs. One task is created per contact."},"priority":{"type":"string","required":false,"visibility":"user-or-llm","description":"Task priority: \\"high\\", \\"medium\\", or \\"low\\" (defaults to \\"medium\\")"},"due_at":{"type":"string","required":true,"visibility":"user-or-llm","description":"Due date/time in ISO 8601 format (e.g., \\"2024-12-31T23:59:59Z\\")"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task type: \\"call\\", \\"outreach_manual_email\\", \\"linkedin_step_connect\\", \\"linkedin_step_message\\", \\"linkedin_step_view_profile\\", \\"linkedin_step_interact_post\\", or \\"action_item\\""},"status":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task status: \\"scheduled\\", \\"completed\\", or \\"skipped\\""},"note":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-form note providing context for the task"}}},"apollo_task_search":{"id":"apollo_task_search","name":"Apollo Search Tasks","description":"Search for tasks in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"task_due_at\\" or \\"task_priority\\""},"open_factor_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Filter by status. Common values: [\\"task_types\\"] for open tasks, [\\"task_completed_at\\"] for completed tasks."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}}},"appconfig_create_application":{"id":"appconfig_create_application","name":"AppConfig Create Application","description":"Create an application in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the application to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the application"}}},"appconfig_create_configuration_profile":{"id":"appconfig_create_configuration_profile","name":"AppConfig Create Configuration Profile","description":"Create a configuration profile in an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the configuration profile in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the configuration profile"},"locationUri":{"type":"string","required":true,"visibility":"user-or-llm","description":"Where the configuration is stored. Use \\"hosted\\" for AppConfig-hosted configurations, or an SSM/S3 URI"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"ARN of an IAM role to retrieve the configuration (required for non-hosted URIs)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Profile type: AWS.Freeform (default) or AWS.AppConfig.FeatureFlags"}}},"appconfig_create_environment":{"id":"appconfig_create_environment","name":"AppConfig Create Environment","description":"Create an environment for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the environment in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the environment to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the environment"}}},"appconfig_create_hosted_configuration_version":{"id":"appconfig_create_hosted_configuration_version","name":"AppConfig Create Hosted Configuration Version","description":"Create a new hosted configuration version for an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to add the version to"},"content":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration content (e.g., a JSON or YAML document)"},"contentType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Content type of the configuration (e.g., application/json, text/plain)"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration version"},"latestVersionNumber":{"type":"number","required":false,"visibility":"user-or-llm","description":"The version number of the latest version, used for optimistic concurrency"},"versionLabel":{"type":"string","required":false,"visibility":"user-or-llm","description":"A user-defined label for the configuration version"}}},"appconfig_delete_application":{"id":"appconfig_delete_application","name":"AppConfig Delete Application","description":"Delete an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to delete"}}},"appconfig_delete_configuration_profile":{"id":"appconfig_delete_configuration_profile","name":"AppConfig Delete Configuration Profile","description":"Delete an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to delete"}}},"appconfig_delete_environment":{"id":"appconfig_delete_environment","name":"AppConfig Delete Environment","description":"Delete an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to delete"}}},"appconfig_delete_hosted_configuration_version":{"id":"appconfig_delete_hosted_configuration_version","name":"AppConfig Delete Hosted Configuration Version","description":"Delete a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID that owns the version"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to delete"}}},"appconfig_get_application":{"id":"appconfig_get_application","name":"AppConfig Get Application","description":"Get details about a single AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to retrieve"}}},"appconfig_get_configuration":{"id":"appconfig_get_configuration","name":"AppConfig Get Configuration","description":"Retrieve the latest deployed configuration for an AppConfig application, environment, and profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID or name to retrieve configuration for"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID or name to retrieve configuration for"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID or name to retrieve"}}},"appconfig_get_configuration_profile":{"id":"appconfig_get_configuration_profile","name":"AppConfig Get Configuration Profile","description":"Get details about a single AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to retrieve"}}},"appconfig_get_deployment":{"id":"appconfig_get_deployment","name":"AppConfig Get Deployment","description":"Get details about a specific AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment"}}},"appconfig_get_environment":{"id":"appconfig_get_environment","name":"AppConfig Get Environment","description":"Get details about a single AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to retrieve"}}},"appconfig_get_hosted_configuration_version":{"id":"appconfig_get_hosted_configuration_version","name":"AppConfig Get Hosted Configuration Version","description":"Retrieve a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to read the version from"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to retrieve"}}},"appconfig_list_applications":{"id":"appconfig_list_applications","name":"AppConfig List Applications","description":"List applications in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of applications to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_list_configuration_profiles":{"id":"appconfig_list_configuration_profiles","name":"AppConfig List Configuration Profiles","description":"List configuration profiles for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profiles"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of configuration profiles to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_list_deployment_strategies":{"id":"appconfig_list_deployment_strategies","name":"AppConfig List Deployment Strategies","description":"List deployment strategies available in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployment strategies to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_list_deployments":{"id":"appconfig_list_deployments","name":"AppConfig List Deployments","description":"List deployments for an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployments"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_list_environments":{"id":"appconfig_list_environments","name":"AppConfig List Environments","description":"List environments for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of environments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_list_hosted_configuration_versions":{"id":"appconfig_list_hosted_configuration_versions","name":"AppConfig List Hosted Configuration Versions","description":"List hosted configuration versions for an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to list versions for"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of versions to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}}},"appconfig_start_deployment":{"id":"appconfig_start_deployment","name":"AppConfig Start Deployment","description":"Start deploying a configuration version to an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to deploy in"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to deploy to"},"deploymentStrategyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The deployment strategy ID to use"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to deploy"},"configurationVersion":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration version to deploy"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the deployment"}}},"appconfig_stop_deployment":{"id":"appconfig_stop_deployment","name":"AppConfig Stop Deployment","description":"Stop an in-progress AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment to stop"}}},"appconfig_update_application":{"id":"appconfig_update_application","name":"AppConfig Update Application","description":"Update the name or description of an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the application"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the application"}}},"appconfig_update_configuration_profile":{"id":"appconfig_update_configuration_profile","name":"AppConfig Update Configuration Profile","description":"Update the name, description, or retrieval role of an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the configuration profile"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"New ARN of the IAM role used to retrieve the configuration"}}},"appconfig_update_environment":{"id":"appconfig_update_environment","name":"AppConfig Update Environment","description":"Update the name or description of an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the environment"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the environment"}}},"arxiv_get_author_papers":{"id":"arxiv_get_author_papers","name":"ArXiv Get Author Papers","description":"Search for papers by a specific author on ArXiv.","version":"1.0.0","params":{"authorName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Author name to search for"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"}}},"arxiv_get_paper":{"id":"arxiv_get_paper","name":"ArXiv Get Paper","description":"Get detailed information about a specific ArXiv paper by its ID.","version":"1.0.0","params":{"paperId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ArXiv paper ID (e.g., \\"1706.03762\\")"}}},"arxiv_search":{"id":"arxiv_search","name":"ArXiv Search","description":"Search for academic papers on ArXiv by keywords, authors, titles, or other fields.","version":"1.0.0","params":{"searchQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query to execute"},"searchField":{"type":"string","required":false,"visibility":"user-only","description":"Field to search in: all, ti (title), au (author), abs (abstract), co (comment), jr (journal), cat (category), rn (report number)"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"},"sortBy":{"type":"string","required":false,"visibility":"user-only","description":"Sort by: relevance, lastUpdatedDate, submittedDate (default: relevance)"},"sortOrder":{"type":"string","required":false,"visibility":"user-only","description":"Sort order: ascending, descending (default: descending)"}}},"asana_add_comment":{"id":"asana_add_comment","name":"Asana Add Comment","description":"Add a comment (story) to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string)"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The text content of the comment"}},"oauth":{"required":true,"provider":"asana"}},"asana_add_followers":{"id":"asana_add_followers","name":"Asana Add Followers","description":"Add one or more followers to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task (numeric string)"},"followers":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of user GIDs to add as followers to the task"}},"oauth":{"required":true,"provider":"asana"}},"asana_create_project":{"id":"asana_create_project","name":"Asana Create Project","description":"Create a new project in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the project will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the project"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the project"}},"oauth":{"required":true,"provider":"asana"}},"asana_create_section":{"id":"asana_create_section","name":"Asana Create Section","description":"Create a new section in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to add the section to"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the section"}},"oauth":{"required":true,"provider":"asana"}},"asana_create_subtask":{"id":"asana_create_subtask","name":"Asana Create Subtask","description":"Create a subtask under an existing Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the parent Asana task (numeric string)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the subtask"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the subtask"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the subtask to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"}},"asana_create_task":{"id":"asana_create_task","name":"Asana Create Task","description":"Create a new task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the task will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the task to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"}},"asana_delete_task":{"id":"asana_delete_task","name":"Asana Delete Task","description":"Delete an Asana task by its GID (moves it to the trash)","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task to delete (numeric string)"}},"oauth":{"required":true,"provider":"asana"}},"asana_get_project":{"id":"asana_get_project","name":"Asana Get Project","description":"Retrieve a single Asana project by its GID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to retrieve"}},"oauth":{"required":true,"provider":"asana"}},"asana_get_projects":{"id":"asana_get_projects","name":"Asana Get Projects","description":"Retrieve all projects from an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to retrieve projects from"}},"oauth":{"required":true,"provider":"asana"}},"asana_get_task":{"id":"asana_get_task","name":"Asana Get Task","description":"Retrieve a single task by GID or get multiple tasks with filters","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":false,"visibility":"user-or-llm","description":"The globally unique identifier (GID) of the task. If not provided, will get multiple tasks."},"workspace":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to filter tasks (required when not using taskGid)"},"project":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to filter tasks"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of tasks to return (default: 50)"}},"oauth":{"required":true,"provider":"asana"}},"asana_list_sections":{"id":"asana_list_sections","name":"Asana List Sections","description":"List all sections in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to list sections from"}},"oauth":{"required":true,"provider":"asana"}},"asana_list_workspaces":{"id":"asana_list_workspaces","name":"Asana List Workspaces","description":"List all Asana workspaces and organizations the authenticated user belongs to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"}},"oauth":{"required":true,"provider":"asana"}},"asana_search_tasks":{"id":"asana_search_tasks","name":"Asana Search Tasks","description":"Search for tasks in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to search tasks in"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Text to search for in task names"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter tasks by assignee user GID"},"projects":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of Asana project GIDs (numeric strings) to filter tasks by"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Filter by completion status"}},"oauth":{"required":true,"provider":"asana"}},"asana_update_task":{"id":"asana_update_task","name":"Asana Update Task","description":"Update an existing task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string) of the task to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated name for the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated assignee user GID"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Mark task as completed or not completed"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"}},"ashby_add_candidate_tag":{"id":"ashby_add_candidate_tag","name":"Ashby Add Candidate Tag","description":"Adds a tag to a candidate in Ashby and returns the updated candidate.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the tag to"},"tagId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the tag to add"}}},"ashby_change_application_stage":{"id":"ashby_change_application_stage","name":"Ashby Change Application Stage","description":"Moves an application to a different interview stage. Requires an archive reason when moving to an Archived stage.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the application to update the stage of"},"interviewStageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the interview stage to move the application to"},"archiveReasonId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Archive reason UUID. Required when moving to an Archived stage, ignored otherwise"}}},"ashby_create_application":{"id":"ashby_create_application","name":"Ashby Create Application","description":"Creates a new application for a candidate on a job. Optionally specify interview plan, stage, source, and credited user.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to consider for the job"},"jobId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the job to consider the candidate for"},"interviewPlanId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview plan to use (defaults to the job default plan)"},"interviewStageId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview stage to place the application in (defaults to first Lead stage)"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to set on the application"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the user the application is credited to"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to set as the application creation date (defaults to now)"}}},"ashby_create_candidate":{"id":"ashby_create_candidate","name":"Ashby Create Candidate","description":"Creates a new candidate record in Ashby.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"The candidate full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary email address for the candidate"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the candidate"},"linkedInUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"githubUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"GitHub profile URL"},"website":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal website URL"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the candidate to"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the Ashby user to credit with sourcing this candidate"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdated creation timestamp in ISO 8601 (e.g. 2024-01-01T00:00:00Z). Defaults to now."},"alternateEmailAddresses":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of additional email address strings to add to the candidate, e.g. [\\"a@x.com\\",\\"b@y.com\\"]"}}},"ashby_create_note":{"id":"ashby_create_note","name":"Ashby Create Note","description":"Creates a note on a candidate in Ashby. Supports plain text and HTML content (bold, italic, underline, links, lists, code).","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the note to"},"note":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note content. If noteType is text/html, supports: , , , ,