diff --git a/apps/desktop/README.md b/apps/desktop/README.md index ea7e867477e..f6c2caafc8f 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -245,8 +245,8 @@ Terminal control), which the helper refuses. With **Ask before using a new app** on, each app's first use is approved for the task or persistently, and persistent approvals are listed in Settings and can be revoked. Sign-out and deployment changes clear opt-in, the ask choice -and approvals. The conversation shows the -active app/action and a Stop button; **⌘⇧Esc** also stops active work when the shortcut is available. +and approvals. Stopping the chat response, or **⌘⇧Esc** when the shortcut is available, stops active +work; Settings shows the action in progress. Stop cancels queued actions, in-flight authorization, approval prompts and the native helper. Already completed input cannot be undone. diff --git a/apps/desktop/native/computer-use/ComputerUse.swift b/apps/desktop/native/computer-use/ComputerUse.swift index bb5b604d746..9f287c330a7 100644 --- a/apps/desktop/native/computer-use/ComputerUse.swift +++ b/apps/desktop/native/computer-use/ComputerUse.swift @@ -16,9 +16,10 @@ struct ComputerError: Error { let dispatchState: String? init(_ code: String, _ message: String, dispatchState: String? = nil) { self.code = code; self.message = message; self.dispatchState = dispatchState } } -struct InputStep: Decodable { let action: String; let text: String?; let key: String? } +/// A request's arguments. An input sequence or batch step is itself a request's arguments, with its action. struct Parameters: Decodable { - var steps: [InputStep]? + var action: String? + var steps: [Parameters]? var activateFirst: Bool? var permission: String? var bundleId: String? @@ -86,6 +87,33 @@ func attribute(_ element: AXUIElement, _ name: String) -> CFTypeRef? { var value: CFTypeRef? return AXUIElementCopyAttributeValue(element, name as CFString, &value) == .success ? value : nil } +/// Reads several attributes in one Accessibility round trip instead of one per attribute; an attribute +/// the element lacks or fails to return is omitted, as `attribute` would return nil for it. +func attributes(_ element: AXUIElement, _ names: [String]) -> [String: CFTypeRef] { + var raw: CFArray? + let status = AXUIElementCopyMultipleAttributeValues(element, names as CFArray, AXCopyMultipleAttributeOptions(rawValue: 0), &raw) + guard status == .success, let values = raw as [AnyObject]?, values.count == names.count else { + // Only an element that cannot answer a batched read is asked one attribute at a time; a + // timeout or failure would only repeat once per attribute. + guard [.attributeUnsupported, .illegalArgument, .notImplemented].contains(status) else { return [:] } + return names.reduce(into: [:]) { result, name in result[name] = attribute(element, name) } + } + var result: [String: CFTypeRef] = [:] + for (name, value) in zip(names, values) { + if value is NSNull { continue } + if CFGetTypeID(value) == AXValueGetTypeID(), AXValueGetType(unsafeBitCast(value, to: AXValue.self)) == .axError { continue } + result[name] = value + } + return result +} +/// Elements bucketed by CFHash, so a walk's membership and parent lookups stay linear in its size. +struct ElementIndex { + private var buckets: [CFHashCode: [(element: AXUIElement, id: String)]] = [:] + func id(of element: AXUIElement) -> String? { buckets[CFHash(element)]?.first { CFEqual($0.element, element) }?.id } + mutating func insert(_ element: AXUIElement, id: String) { buckets[CFHash(element), default: []].append((element, id)) } +} +let NODE_ATTRIBUTES = [kAXRoleAttribute, kAXSubroleAttribute, kAXTitleAttribute, kAXDescriptionAttribute, kAXPlaceholderValueAttribute, + kAXHelpAttribute, kAXValueAttribute, kAXFocusedAttribute, kAXEnabledAttribute, kAXPositionAttribute, kAXSizeAttribute] func secure(_ element: AXUIElement) -> Bool { attribute(element, kAXSubroleAttribute) as? String == kAXSecureTextFieldSubrole } @@ -135,8 +163,11 @@ func axActions(_ element: AXUIElement) -> [String] { return AXUIElementCopyActionNames(element, &names) == .success ? names as? [String] ?? [] : [] } func elementRect(_ element: AXUIElement) -> CGRect? { - guard let rawPosition = attribute(element, kAXPositionAttribute), CFGetTypeID(rawPosition) == AXValueGetTypeID(), - let rawSize = attribute(element, kAXSizeAttribute), CFGetTypeID(rawSize) == AXValueGetTypeID() else { return nil } + axRect(position: attribute(element, kAXPositionAttribute), size: attribute(element, kAXSizeAttribute)) +} +func axRect(position rawPosition: CFTypeRef?, size rawSize: CFTypeRef?) -> CGRect? { + guard let rawPosition, CFGetTypeID(rawPosition) == AXValueGetTypeID(), + let rawSize, CFGetTypeID(rawSize) == AXValueGetTypeID() else { return nil } var position = CGPoint.zero; var size = CGSize.zero guard AXValueGetValue(unsafeBitCast(rawPosition, to: AXValue.self), .cgPoint, &position), AXValueGetValue(unsafeBitCast(rawSize, to: AXValue.self), .cgSize, &size) else { return nil } @@ -309,12 +340,14 @@ final class Snapshot { queue.append(AXTraversalEntry(element: unsafeBitCast(raw, to: AXUIElement.self), parent: root, depth: 1, menu: false, priority: true)) } var menuCount = 0 + var index = ElementIndex() while nodes.count < 2000 && Date().timeIntervalSince(created) < 8, let entry = queue.next() { try checkCancellation() let element = entry.element AXUIElementSetMessagingTimeout(element, 0.25) - if elements.values.contains(where: { CFEqual($0, element) }) { continue } - let role = attribute(element, kAXRoleAttribute) as? String ?? "AXUnknown" + if index.id(of: element) != nil { continue } + let values = attributes(element, NODE_ATTRIBUTES) + let role = values[kAXRoleAttribute] as? String ?? "AXUnknown" let isMenu = entry.menu || [kAXMenuBarRole, kAXMenuRole, kAXMenuItemRole, kAXMenuBarItemRole].contains(role) if role == kAXMenuItemRole && !entry.menu && !entry.priority { queue.append(AXTraversalEntry(element: element, parent: entry.parent, depth: entry.depth, menu: true, priority: false)); continue @@ -323,27 +356,28 @@ final class Snapshot { guard menuCount < 80 else { truncated = true; continue } menuCount += 1 } - let id = "e\(nodes.count)"; elements[id] = element + let id = "e\(nodes.count)"; elements[id] = element; index.insert(element, id: id) var node: [String: Any] = ["elementId": id, "role": role, "actions": [String]()] - if let parent = entry.parent, let parentID = elements.first(where: { CFEqual($0.value, parent) })?.key { node["parentId"] = parentID } - let isSecure = secure(element) - var labels = [kAXTitleAttribute, kAXDescriptionAttribute].compactMap { attribute(element, $0) as? String }.filter { !$0.isEmpty } - if labels.isEmpty { labels = [kAXPlaceholderValueAttribute, kAXHelpAttribute].compactMap { attribute(element, $0) as? String }.filter { !$0.isEmpty } } + if let parent = entry.parent, let parentID = index.id(of: parent) { node["parentId"] = parentID } + let isSecure = values[kAXSubroleAttribute] as? String == kAXSecureTextFieldSubrole + var labels = [kAXTitleAttribute, kAXDescriptionAttribute].compactMap { values[$0] as? String }.filter { !$0.isEmpty } + if labels.isEmpty { labels = [kAXPlaceholderValueAttribute, kAXHelpAttribute].compactMap { values[$0] as? String }.filter { !$0.isEmpty } } if role == kAXMenuItemRole, let shortcut = menuShortcut(element) { labels.append(shortcut) } if !isSecure, !labels.isEmpty { node["label"] = String(labels.joined(separator: " ").prefix(1024)) } - if !isSecure, let raw = attribute(element, kAXValueAttribute) { + if !isSecure, let raw = values[kAXValueAttribute] { if let value = raw as? String { node["value"] = String(value.prefix(2048)) } else if let value = raw as? NSNumber { node["value"] = value.stringValue } } if !isSecure { - if let focused = attribute(element, kAXFocusedAttribute) as? Bool { node["focused"] = focused } + if let focused = values[kAXFocusedAttribute] as? Bool { node["focused"] = focused } var writable: DarwinBoolean = false - let writableValue = AXUIElementIsAttributeSettable(element, kAXValueAttribute as CFString, &writable) == .success && writable.boolValue - node["editable"] = [kAXTextAreaRole, kAXTextFieldRole].contains(role) && writableValue - if let placeholder = attribute(element, kAXPlaceholderValueAttribute) as? String { node["placeholder"] = String(placeholder.prefix(1024)) } + let writableValue = [kAXTextAreaRole, kAXTextFieldRole].contains(role) + && AXUIElementIsAttributeSettable(element, kAXValueAttribute as CFString, &writable) == .success && writable.boolValue + node["editable"] = writableValue + if let placeholder = values[kAXPlaceholderValueAttribute] as? String { node["placeholder"] = String(placeholder.prefix(1024)) } } - if let enabled = attribute(element, kAXEnabledAttribute) as? Bool { node["enabled"] = enabled } - if let rect = elementRect(element) { node.merge(rectJSON(rect)) { _, new in new } } + if let enabled = values[kAXEnabledAttribute] as? Bool { node["enabled"] = enabled } + if let rect = axRect(position: values[kAXPositionAttribute], size: values[kAXSizeAttribute]) { node.merge(rectJSON(rect)) { _, new in new } } var actions: CFArray? if AXUIElementCopyActionNames(element, &actions) == .success { node["actions"] = Array((actions as? [String] ?? []).prefix(128)).map { String($0.prefix(128)) } } nodes.append(node) @@ -430,7 +464,34 @@ func parseKey(_ key: String) throws -> (CGKeyCode, CGEventFlags) { let aliases = ["esc": "escape", "arrowleft": "left", "arrowright": "right", "arrowup": "up", "arrowdown": "down", "forwarddelete": "delete", "pgup": "pageup", "pgdn": "pagedown", "pgdown": "pagedown", "minus": "-", "equal": "=", "equals": "=", "comma": ",", "period": ".", "slash": "/", "semicolon": ";", "quote": "'", "backslash": "\\", "backtick": "`", "grave": "`", "leftbracket": "[", "rightbracket": "]"] guard let code = keys[aliases[name] ?? name] else { throw ComputerError("unsupported_key", "Unsupported key name. Use names such as Enter, Tab, Escape, Backspace, Delete (forward delete), Left, PageUp, F5, or a single character, with Cmd/Shift/Alt/Ctrl modifiers.") }; return (code, flags) } -func validateInputSteps(_ steps: [InputStep]) throws { +/// The actions a batch may run; each is validated again by its own action path when it runs. +let BATCH_ACTIONS: Set = ["click", "type_text", "set_value", "press_key", "scroll", "perform_action"] +func validateBatchSteps(_ steps: [Parameters]) throws { + guard (2...8).contains(steps.count) else { throw ComputerError("invalid_arguments", "A batch requires 2 to 8 steps.") } + var units = 0 + for step in steps { + guard let action = step.action, BATCH_ACTIONS.contains(action), step.bundleId == nil, step.snapshotId == nil else { + throw ComputerError("invalid_arguments", "Batch steps are click, type_text, set_value, press_key, scroll or perform_action, without their own app or snapshot.") + } + // Malformed steps are refused before step 1 runs, so a typo never leaves a half-applied batch. + switch action { + case "type_text": _ = try required(step.elementId, "elementId"); _ = try required(step.text, "text") + case "set_value": _ = try required(step.elementId, "elementId"); _ = try required(step.value, "value") + case "perform_action": _ = try required(step.elementId, "elementId"); _ = try required(step.accessibilityAction, "accessibilityAction") + case "press_key": _ = try parseKey(required(step.key, "key")) + case "click": + guard ["left", "right"].contains(step.button ?? "left"), (1...3).contains(step.clickCount ?? 1) else { throw ComputerError("invalid_arguments", "Invalid button or clickCount.") } + guard step.elementId != nil || (step.x != nil && step.y != nil) else { throw ComputerError("invalid_arguments", "A click step needs an elementId or x and y.") } + case "scroll": + guard step.elementId != nil || (step.x != nil && step.y != nil) else { throw ComputerError("invalid_arguments", "A scroll step needs an elementId or x and y.") } + guard step.deltaX != nil || step.deltaY != nil else { throw ComputerError("invalid_arguments", "A scroll step needs deltaX or deltaY.") } + default: break + } + units += (step.text?.utf16.count ?? 0) + (step.value?.utf16.count ?? 0) + } + guard units <= 32000 else { throw ComputerError("invalid_arguments", "Combined batch text exceeds 32000 UTF-16 units.") } +} +func validateInputSteps(_ steps: [Parameters]) throws { guard (1...32).contains(steps.count) else { throw ComputerError("invalid_arguments", "Input sequence requires 1 to 32 steps.") } var units = 0 for step in steps { @@ -546,6 +607,57 @@ func postMouse(pid: pid_t, type: CGEventType, point: CGPoint, button: CGMouseBut event.post(tap: .cghidEventTap) } +/// Clicks the center of an observed text control so it takes keyboard focus, only when that point lands on +/// the control (or a text control inside it) within its observed window; otherwise nothing is sent and the focus check decides. +@MainActor +func focusByClicking(_ element: AXUIElement, pid: pid_t, snapshot: Snapshot, caretIrrelevant: Bool) async throws { + guard let rect = elementRect(element), let windowID = snapshot.windowId(holding: element), + let frame = snapshot.windowFrames[windowID], let window = snapshot.windowElements[windowID] else { return } + let center = CGPoint(x: rect.midX, y: rect.midY) + // The point must land on the editor or a text control inside it, never a button or row nested in a container. + guard takesText(element), frame.contains(center), let hit = elementAtPoint(center) else { return } + let insideEditor = try takesText(hit) && ancestorPath(hit, to: element) != nil + // Secure inputs stay untargetable, so a password field is never clicked into focus either. + guard CFEqual(hit, element) || insideEditor, (try? requireNonSecure(hit)) != nil else { return } + do { + try await coordinateAction { dispatch in + try checkCancellation() + try postMouse(pid: pid, type: .leftMouseDown, point: center, button: .left, windowID: windowID, window: window, frame: frame, dispatch: dispatch) + try postMouse(pid: pid, type: .leftMouseUp, point: center, button: .left, windowID: windowID, window: window, frame: frame, release: true, dispatch: dispatch) + } + } catch let error as ComputerError where error.dispatchState == "not_started" { + // A mouse guard refused the point before anything was sent; the focus check reports the outcome. + return + } + // A click leaves the caret where it landed; typing inserts at the end, as after an Accessibility focus. + let root = AXUIElementCreateApplication(pid) + for _ in 0..<5 { + try await Task.sleep(for: .milliseconds(50)) + guard let focused = attribute(root, kAXFocusedUIElementAttribute), holdsFocus(focused, element) else { continue } + if caretIrrelevant || caretAtEnd(unsafeBitCast(focused, to: AXUIElement.self)) { return } + throw ComputerError("caret_unconfirmed", "The editor was clicked into focus, but its caret could not be confirmed at the end, so no text was sent. Observe the app, then use set_value, or start an input_sequence with Cmd+A.") + } + throw ComputerError("caret_unconfirmed", "The editor was clicked, but it did not take keyboard focus in time, so no text was sent. Observe the app before retrying.") +} +/// Whether the caret is at the end of `editor`'s text, moving it there when the editor allows: an empty +/// editor qualifies, and a move counts only once the selection range reads back at the end. +func caretAtEnd(_ editor: AXUIElement) -> Bool { + guard let length = (attribute(editor, kAXValueAttribute) as? String)?.utf16.count + ?? (attribute(editor, kAXNumberOfCharactersAttribute) as? NSNumber)?.intValue else { return false } + if length == 0 { return true } + func selection() -> CFRange? { + guard let raw = attribute(editor, kAXSelectedTextRangeAttribute), CFGetTypeID(raw) == AXValueGetTypeID() else { return nil } + var range = CFRange(); return AXValueGetValue(unsafeBitCast(raw, to: AXValue.self), .cfRange, &range) ? range : nil + } + if let range = selection(), range.location == length, range.length == 0 { return true } + var settable: DarwinBoolean = false + var end = CFRange(location: length, length: 0) + guard AXUIElementIsAttributeSettable(editor, kAXSelectedTextRangeAttribute as CFString, &settable) == .success, settable.boolValue, + let value = AXValueCreate(.cfRange, &end), + AXUIElementSetAttributeValue(editor, kAXSelectedTextRangeAttribute as CFString, value) == .success, + let moved = selection() else { return false } + return moved.location == length && moved.length == 0 +} func discoverApps() throws -> [[String: Any]] { var entries: [String: [String: Any]] = [:] let roots = [URL(fileURLWithPath: "/Applications"), URL(fileURLWithPath: "/System/Applications"), FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications")] @@ -670,7 +782,7 @@ final class Driver { } result["isActive"] = app.isActive return result - case "click", "type_text", "input_sequence", "press_key", "scroll", "drag", "set_value", "perform_action": break + case "click", "type_text", "input_sequence", "press_key", "scroll", "drag", "set_value", "perform_action", "batch": break default: throw ComputerError("unknown_method", "Unknown computer-use method.") } try requireAccessibility() @@ -695,6 +807,37 @@ final class Driver { let p = request.params let pid = app.processIdentifier switch request.method { + case "batch": + let steps = try required(p.steps, "steps") + try validateBatchSteps(steps) + var completed = 0 + func stopped(_ message: String) -> [String: Any] { + ["kind": "action", "action": "batch", "bundleId": bundle, "dispatched": true, "verified": false, + "sequence": ["completedSteps": completed, "totalSteps": steps.count, "error": String((message + " The remaining steps were not run; observe the app before continuing, and do not retry the batch.").prefix(2000))]] + } + for (index, step) in steps.enumerated() { + // Stop can arrive during the previous step's render wait; no later step may start after it. + do { try checkCancellation() } catch { + // Before any step ran nothing was sent, so Stop fails the batch as cancelled. + if completed == 0 { throw error } + return stopped("Stopped before step \(index + 1).") + } + let before = uiStructure(pid: pid) + do { _ = try await act(Request(id: request.id, method: try required(step.action, "action"), params: step), app: app, bundle: bundle, snapshot: snapshot) } + catch { + // A first step that sent nothing fails like the single action, keeping the snapshot valid. + if completed == 0 && !inputDispatched { + guard let error = error as? ComputerError else { throw error } + throw ComputerError(error.code, "Batch step 1 failed: " + error.message, dispatchState: error.dispatchState) + } + return stopped("Step \(index + 1) failed: " + ((error as? ComputerError)?.message ?? "its outcome is unknown.")) + } + completed += 1 + if completed < steps.count, uiStructure(pid: pid) != before { + return stopped("Step \(index + 1) opened or closed a window, sheet, menu or popover.") + } + } + return ["kind": "action", "action": "batch", "bundleId": bundle, "dispatched": true, "verified": false, "sequence": ["completedSteps": completed, "totalSteps": steps.count]] case "perform_action": let element = try snapshot.element(p.elementId); let action = try required(p.accessibilityAction, "accessibilityAction") var names: CFArray?; try axCheck(AXUIElementCopyActionNames(element, &names)) @@ -731,7 +874,7 @@ final class Driver { } case "type_text", "input_sequence": let element = try snapshot.element(p.elementId) - let steps = request.method == "type_text" ? [InputStep(action: "type_text", text: try required(p.text, "text"), key: nil)] : try required(p.steps, "steps") + let steps = request.method == "type_text" ? [Parameters(action: "type_text", text: try required(p.text, "text"))] : try required(p.steps, "steps") try validateInputSteps(steps) let root = AXUIElementCreateApplication(pid) // Keystrokes go to whatever holds keyboard focus: a text control (terminals included, read-only @@ -744,7 +887,18 @@ final class Driver { // Activation can move focus, so whether the editor holds it is read after activating. if attribute(root, kAXFocusedUIElementAttribute).map({ holdsFocus($0, element) }) != true { try axCheck(AXUIElementSetAttributeValue(element, kAXFocusedAttribute as CFString, kCFBooleanTrue)) - try await Task.sleep(for: .milliseconds(50)) + // Focus can settle asynchronously; slow apps get 250 ms before any click is considered. + for _ in 0..<5 { + try await Task.sleep(for: .milliseconds(50)) + if attribute(root, kAXFocusedUIElementAttribute).map({ holdsFocus($0, element) }) == true { break } + } + // Some apps (SwiftUI sheets such as Clock's city search) ignore an Accessibility focus + // request; in a foreground app the field is focused the way a user would, by clicking it. + if app.isActive, attribute(root, kAXFocusedUIElementAttribute).map({ holdsFocus($0, element) }) != true { + // Select-all as the first step replaces the whole value, so where the click put the caret is moot. + let selectsAll = try steps.first.map { $0.action == "press_key" ? try parseKey(required($0.key, "key")) == (0, .maskCommand) : false } ?? false + try await focusByClicking(element, pid: pid, snapshot: snapshot, caretIrrelevant: selectsAll) + } } let observedWindows = snapshot.observedWindows() // The first editor found holding focus receives the whole input, so text never follows focus elsewhere. @@ -768,7 +922,7 @@ final class Driver { do { try validateFocus(); break } catch let error as ComputerError where error.code == "focus_changed" { if attempt < 9 { try await Task.sleep(for: .milliseconds(50)) } - else if !app.isActive { throw ComputerError("activation_required", "No input was dispatched. This background app does not expose the exact keyboard focus target. Call activate_app for this bundle, then get_app_state and retry using the fresh editor reference.", dispatchState: "not_started") } + else if !app.isActive && !inputDispatched { throw ComputerError("activation_required", "No input was dispatched. This background app does not expose the exact keyboard focus target. Call activate_app for this bundle, then get_app_state and retry using the fresh editor reference.", dispatchState: "not_started") } else { throw error } } } @@ -879,6 +1033,23 @@ final class Driver { return ["kind": "action", "action": request.method, "bundleId": bundle, "dispatched": true, "verified": false] } } +/// The app's window structure by identity: its on-screen windows (sheets, menus and popovers are +/// windows of their own), the focused window, and that window's direct content, where a sheet sits. +/// Identities rather than counts, so switching one open menu or sheet for another still registers. +func uiStructure(pid: pid_t) -> [Int] { + let root = AXUIElementCreateApplication(pid) + AXUIElementSetMessagingTimeout(root, 0.1) + var structure = (CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: Any]] ?? []) + .filter { ($0[kCGWindowOwnerPID as String] as? NSNumber)?.int32Value == pid } + .compactMap { ($0[kCGWindowNumber as String] as? NSNumber)?.intValue } + .sorted() + if let raw = attribute(root, kAXFocusedWindowAttribute), CFGetTypeID(raw) == AXUIElementGetTypeID() { + structure.append(Int(bitPattern: UInt(CFHash(raw)))) + let (children, _) = pagedElements(unsafeBitCast(raw, to: AXUIElement.self), name: kAXChildrenAttribute, limit: 64) + structure.append(contentsOf: children.map { Int(bitPattern: UInt(CFHash($0))) }) + } + return structure +} func nextLine() throws -> String? { var bytes = [UInt8](); bytes.reserveCapacity(4096) var oversized = false diff --git a/apps/desktop/native/computer-use/README.md b/apps/desktop/native/computer-use/README.md index c615ec6649e..31acd652422 100644 --- a/apps/desktop/native/computer-use/README.md +++ b/apps/desktop/native/computer-use/README.md @@ -14,7 +14,7 @@ Coordinates in action inputs are window-local points; node frames are global scr AXPress and AXSetValue are direct semantic operations. Keyboard events target the app PID. Cmd+A uses the focused editor's writable accessibility selection range where supported. Coordinate mouse input requires the target app in front; `activate_app` makes that focus change explicit and confirms it. Click/drag/scroll validate the foreground process, unchanged window geometry and the app that receives the point; the topmost window-list entry is not the click target because Notification Center and the Dock keep full-screen click-through windows. A hit on a window's own background counts as that window. Background tabs and minimized windows share bounds with the window in front, so they take no coordinate input. An action that opens UI (AXPress, AXPick, AXShowMenu) and times out while the app shows a sheet, modal or menu (`cannotComplete`) counts as dispatched; any other action that times out fails as uncertain. A menu-item press that leaves the item's own menu open with the app highlighting that item (Calendar on macOS 26) is completed with Return. Sheets, including Go to Folder and out-of-process open and save panels whose contents report no window, count as the window they are attached to. Menu item labels carry their shortcuts (for example ⇧⌘N). Some apps ignore background keyboard events. `dispatched:true,verified:false` means only dispatch succeeded; the agent must observe again to establish the outcome. No foreground activation fallback is hidden inside the helper. Native key codes currently assume the US layout for shortcuts; literal text uses Unicode events. Unsupported shortcuts fail explicitly. -`type_text` and `input_sequence` target a text control (text fields and areas, terminals, combo boxes, date and time pickers, or anything with an editable text selection), or a container whose focused editor already holds keyboard focus, such as a web composer; keystrokes are sent only while focus is in a text input. `input_sequence` executes 1–32 key/text steps against one such element. It validates every step before dispatch and checks the same editor, window, and secure-input ancestry between steps. Enter can continue into another step when the same editor and window retain focus. Actual focus loss, Tab, or navigation shortcuts end the sequence before later input. Explicit `activateFirst: true` activates the target app inside the batch before input; it never enables automatic refocusing between steps. Partial execution reports completed steps and an error; callers must observe before retrying. Every mutation is followed by fresh state under the same authorization and queue entry; `observeAfter` optionally requests a screenshot. Observation failure is reported separately so the mutation is never replayed. The model receives a compact, bounded projection of the native snapshot; omitted nodes are reported explicitly. +`type_text` and `input_sequence` target a text control (text fields and areas, terminals, combo boxes, date and time pickers, or anything with an editable text selection), or a container whose focused editor already holds keyboard focus, such as a web composer; keystrokes are sent only while focus is in a text input. When an app ignores the Accessibility focus request (SwiftUI sheets such as Clock's city search), a foreground app's editor is clicked at its center to take focus after 250 ms without it, only if that point hit-tests to the editor (or a non-secure text control inside it) within its observed window; a click refused by the mouse guards sends nothing, the caret is moved to the end and read back so typing inserts there (an empty editor needs no move, and an input_sequence starting with Cmd+A skips the check because it replaces the value); if the caret cannot be confirmed at the end, or focus does not settle, nothing is typed and the call fails with `caret_unconfirmed`. The usual focus checks then apply. `input_sequence` executes 1–32 key/text steps against one such element. It validates every step before dispatch and checks the same editor, window, and secure-input ancestry between steps. Enter can continue into another step when the same editor and window retain focus. Actual focus loss, Tab, or navigation shortcuts end the sequence before later input. Explicit `activateFirst: true` activates the target app inside the sequence before input; it never enables automatic refocusing between steps. Partial execution reports completed steps and an error; callers must observe before retrying. `batch` runs 2–8 click, type_text, set_value, press_key, scroll or perform_action steps planned from one snapshot, each through its own action's checks; it stops at the first step that fails and after any step that changes the app's window structure (on-screen windows, which include sheets, menus and popovers, or the focused window's direct children), reports progress the same way, and a first step that sent nothing is refused with `dispatchState: "not_started"`. Every step is validated before the first one runs. The structure check is a heuristic: it misses modals inside web content and UI that appears after the step's render wait, and stops conservatively on tooltips and autocomplete popovers. Every mutation is followed by fresh state under the same authorization and queue entry; `observeAfter` optionally requests a screenshot. Observation failure is reported separately so the mutation is never replayed. The model receives a compact, bounded projection of the native snapshot; omitted nodes are reported explicitly. ## Cancellation @@ -22,7 +22,7 @@ SIGTERM/SIGINT sets a cancellation flag. Traversal, typing and drag loops check ## Tests and limitations -Compile pure native tests with `swiftc -parse-as-library -D COMPUTER_USE_TEST ComputerUse.swift tests/NativeTests.swift -o native-tests`. They cover multi-display negative origins, coordinate bounds/nonfinite input, shortcut modifiers, invalid key handling and cooperative cancellation. Real status and app discovery outputs were validated against the generated worker Zod schema (156 apps, including 147 installed-only apps). +Compile pure native tests with `swiftc -parse-as-library -D COMPUTER_USE_TEST ComputerUse.swift tests/NativeTests.swift -o native-tests`. They cover multi-display negative origins, coordinate bounds/nonfinite input, shortcut modifiers, invalid key handling, input-sequence and batch validation, protected apps and cooperative cancellation. The live fixture also covers batches and click-to-focus for an editor that ignores Accessibility focus. Real status and app discovery outputs were validated against the generated worker Zod schema (156 apps, including 147 installed-only apps). `python3 tests/protocol.py ` checks persistent framing, bounded-request recovery, Unicode decoding, no-prompt status, invalid methods/permissions, permission gates and clean EOF. `tests/Fixture.swift` compiles into a disposable AppKit app with a text field, secure field, increment button/counter, slider, scroll area and a visual-only marker. `python3 tests/live.py ` requires both grants and verifies actual outcomes. `--allow-missing-screen` explicitly reports screenshots blocked rather than passing them; `--existing-fixture` leaves an existing fixture running for coordinated model trials. Give the fixture bundle ID `com.mothership.computer-use-fixture` when packaging it for integration tests. diff --git a/apps/desktop/native/computer-use/tests/Fixture.swift b/apps/desktop/native/computer-use/tests/Fixture.swift index 6445f5bbbfb..bae9f2a4ceb 100644 --- a/apps/desktop/native/computer-use/tests/Fixture.swift +++ b/apps/desktop/native/computer-use/tests/Fixture.swift @@ -8,6 +8,14 @@ final class MarkerView: NSView { } } +/// A field that ignores Accessibility focus requests but takes focus from a click, as SwiftUI sheets do. +class ClickFocusField: NSTextField { + override func setAccessibilityFocused(_ accessibilityFocused: Bool) {} +} +/// A click-only editor that also ignores Accessibility caret moves, so its caret cannot be confirmed. +final class FixedCaretField: ClickFocusField { + override func setAccessibilitySelectedTextRange(_ accessibilitySelectedTextRange: NSRange) {} +} final class FixtureDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! var count = 0 @@ -31,6 +39,9 @@ final class FixtureDelegate: NSObject, NSApplicationDelegate { let field = NSTextField(string: "Fixture text"); field.setAccessibilityIdentifier("fixture-text"); field.setAccessibilityLabel("Fixture editable text") let secure = NSSecureTextField(string: "fixture-only-secret"); secure.setAccessibilityIdentifier("fixture-password"); secure.setAccessibilityLabel("Fixture password") let button = NSButton(title: "Increment", target: self, action: #selector(increment)); button.setAccessibilityIdentifier("fixture-increment") + let clickField = ClickFocusField(string: ""); clickField.setAccessibilityLabel("Fixture click-focus field") + let caretField = FixedCaretField(string: String(repeating: "keep ", count: 40)); caretField.setAccessibilityLabel("Fixture fixed-caret field") + let sheetButton = NSButton(title: "Show sheet", target: self, action: #selector(showSheet)); sheetButton.setAccessibilityIdentifier("fixture-show-sheet") let slider = NSSlider(value: 25, minValue: 0, maxValue: 100, target: nil, action: nil); slider.setAccessibilityLabel("Fixture slider") let time = NSDatePicker(); time.datePickerStyle = .textField; time.datePickerElements = .hourMinute; time.setAccessibilityLabel("Fixture time") time.dateValue = Calendar.current.date(bySettingHour: 10, minute: 15, second: 0, of: Date()) ?? Date(); time.target = self; time.action = #selector(timeChanged(_:)) @@ -43,15 +54,16 @@ final class FixtureDelegate: NSObject, NSApplicationDelegate { scroll.documentView = document scroll.contentView.postsBoundsChangedNotifications = true NotificationCenter.default.addObserver(self, selector: #selector(scrolled(_:)), name: NSView.boundsDidChangeNotification, object: scroll.contentView) - for view in [field, secure, button, counter, slider, time, timeLabel, marker, scrollCounter, scroll] { stack.addArrangedSubview(view) } + for view in [field, secure, button, counter, clickField, caretField, sheetButton, slider, time, timeLabel, marker, scrollCounter, scroll] { stack.addArrangedSubview(view) } window.contentView!.addSubview(stack) - NSLayoutConstraint.activate([stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor, constant: 30), stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor, constant: -30), stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor, constant: 30), field.widthAnchor.constraint(equalTo: stack.widthAnchor), secure.widthAnchor.constraint(equalTo: stack.widthAnchor), slider.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.heightAnchor.constraint(equalToConstant: 230), marker.widthAnchor.constraint(equalToConstant: 100), marker.heightAnchor.constraint(equalToConstant: 70)]) + NSLayoutConstraint.activate([stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor, constant: 30), stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor, constant: -30), stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor, constant: 30), field.widthAnchor.constraint(equalTo: stack.widthAnchor), secure.widthAnchor.constraint(equalTo: stack.widthAnchor), clickField.widthAnchor.constraint(equalTo: stack.widthAnchor), caretField.widthAnchor.constraint(equalTo: stack.widthAnchor), slider.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.heightAnchor.constraint(equalToConstant: 230), marker.widthAnchor.constraint(equalToConstant: 100), marker.heightAnchor.constraint(equalToConstant: 70)]) window.makeKeyAndOrderFront(nil) window.makeFirstResponder(field) NSApplication.shared.activate() print("fixture-ready"); fflush(stdout) } @objc func increment() { count += 1; counter.stringValue = "Count: \(count)" } + @objc func showSheet() { let alert = NSAlert(); alert.messageText = "Fixture sheet"; alert.addButton(withTitle: "Dismiss"); alert.beginSheetModal(for: window) } @objc func timeChanged(_ picker: NSDatePicker) { let time = Calendar.current.dateComponents([.hour, .minute], from: picker.dateValue) timeLabel.stringValue = "Time: \(time.hour ?? 0):\(String(format: "%02d", time.minute ?? 0))" diff --git a/apps/desktop/native/computer-use/tests/NativeTests.swift b/apps/desktop/native/computer-use/tests/NativeTests.swift index 3de6f79b94a..c5a6d238c2c 100644 --- a/apps/desktop/native/computer-use/tests/NativeTests.swift +++ b/apps/desktop/native/computer-use/tests/NativeTests.swift @@ -45,13 +45,25 @@ import AppKit precondition(bounded.next()?.element == 1) precondition(bounded.next()?.element == 2) precondition(bounded.next() == nil) - try validateInputSteps([InputStep(action: "press_key", text: nil, key: "Cmd+A"), InputStep(action: "type_text", text: "hello 🌍", key: nil)]) - try validateInputSteps((1...5).flatMap { [InputStep(action: "type_text", text: String($0), key: nil), InputStep(action: "press_key", text: nil, key: "Enter")] }) + try validateInputSteps([Parameters(action: "press_key", key: "Cmd+A"), Parameters(action: "type_text", text: "hello 🌍")]) + try validateInputSteps((1...5).flatMap { [Parameters(action: "type_text", text: String($0)), Parameters(action: "press_key", key: "Enter")] }) expectError("invalid_arguments") { try validateInputSteps([]) } - expectError("invalid_arguments") { try validateInputSteps(Array(repeating: InputStep(action: "type_text", text: "x", key: nil), count: 33)) } - expectError("invalid_arguments") { try validateInputSteps([InputStep(action: "type_text", text: String(repeating: "🌍", count: 16001), key: nil)]) } - expectError("invalid_arguments") { try validateInputSteps([InputStep(action: "click", text: nil, key: nil)]) } - expectError("unsupported_key") { try validateInputSteps([InputStep(action: "press_key", text: nil, key: "F99")]) } + expectError("invalid_arguments") { try validateInputSteps(Array(repeating: Parameters(action: "type_text", text: "x"), count: 33)) } + expectError("invalid_arguments") { try validateInputSteps([Parameters(action: "type_text", text: String(repeating: "🌍", count: 16001))]) } + expectError("invalid_arguments") { try validateInputSteps([Parameters(action: "click")]) } + expectError("unsupported_key") { try validateInputSteps([Parameters(action: "press_key", key: "F99")]) } + try validateBatchSteps([Parameters(action: "set_value", elementId: "e1", value: "Name"), Parameters(action: "click", elementId: "e2")]) + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", elementId: "e1")]) } + expectError("invalid_arguments") { try validateBatchSteps(Array(repeating: Parameters(action: "press_key", key: "Tab"), count: 9)) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "drag"), Parameters(action: "press_key", key: "Enter")]) } + expectError("unsupported_key") { try validateBatchSteps([Parameters(action: "click", elementId: "e1"), Parameters(action: "press_key", key: "F99")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", elementId: "e1"), Parameters(action: "click")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", elementId: "e1"), Parameters(action: "scroll", elementId: "e2")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", elementId: "e1"), Parameters(action: "type_text", elementId: "e2")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "set_value", value: "x"), Parameters(action: "click", elementId: "e1")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", elementId: "e1", clickCount: 4), Parameters(action: "press_key", key: "Enter")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "click", bundleId: "com.example", elementId: "e1"), Parameters(action: "press_key", key: "Enter")]) } + expectError("invalid_arguments") { try validateBatchSteps([Parameters(action: "set_value", elementId: "e1", value: String(repeating: "🌍", count: 8001)), Parameters(action: "type_text", elementId: "e2", text: String(repeating: "🌍", count: 8000))]) } let tabFocus = try keyMayChangeFocus("Tab"); precondition(tabFocus) let submitFocus = try keyMayChangeFocus("Enter"); precondition(!submitFocus) let searchFocus = try keyMayChangeFocus("Cmd+K"); precondition(searchFocus) diff --git a/apps/desktop/native/computer-use/tests/live.py b/apps/desktop/native/computer-use/tests/live.py index f6ddf624952..dbd97b93650 100644 --- a/apps/desktop/native/computer-use/tests/live.py +++ b/apps/desktop/native/computer-use/tests/live.py @@ -130,7 +130,86 @@ def coordinate(snapshot, node, fx=0.5, fy=0.5): button = find(snapshot, role='AXButton', label='Increment') snapshot = mutate('perform_action', snapshot, elementId=button['elementId'], accessibilityAction='AXPress') assert any(f'Count: {baseline + 2}' in node.get('value', '') or f'Count: {baseline + 2}' in node.get('label', '') for node in snapshot['nodes']), 'Explicit AX action did not increment' - print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling, text refused by a slider, typing into a date picker, a window target typing into its focused editor, a title-bar click; every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED')) + def count(snapshot): + return int(next(node.get('value', node.get('label', '')) for node in snapshot['nodes'] if node.get('value', node.get('label', '')).startswith('Count: ')).split(': ')[1]) + + def batch(snapshot, steps): + result = call('batch', bundleId=bundle, snapshotId=snapshot['snapshotId'], steps=steps) + assert result['action'] == 'batch' and result['dispatched'] and not result['verified'], result + return result['sequence'], state() + + snapshot = state() + before = count(snapshot) + field = find(snapshot, role='AXTextField', label='Fixture editable text') + button = find(snapshot, role='AXButton', label='Increment') + progress, snapshot = batch(snapshot, [ + {'action': 'set_value', 'elementId': field['elementId'], 'value': 'Batched'}, + {'action': 'click', 'elementId': button['elementId']}, + {'action': 'perform_action', 'elementId': button['elementId'], 'accessibilityAction': 'AXPress'}, + ]) + assert progress == {'completedSteps': 3, 'totalSteps': 3}, progress + assert find(snapshot, role='AXTextField', label='Fixture editable text')['value'] == 'Batched' and count(snapshot) == before + 2, 'Batch steps did not all apply' + slider = find(snapshot, role='AXSlider') + progress, snapshot = batch(snapshot, [ + {'action': 'click', 'elementId': find(snapshot, role='AXButton', label='Increment')['elementId']}, + {'action': 'type_text', 'elementId': slider['elementId'], 'text': 'x'}, + {'action': 'click', 'elementId': find(snapshot, role='AXButton', label='Increment')['elementId']}, + ]) + assert progress['completedSteps'] == 1 and 'Step 2 failed' in progress['error'] and count(snapshot) == before + 3, progress + error = refused('batch', snapshot, steps=[ + {'action': 'type_text', 'elementId': find(snapshot, role='AXSlider')['elementId'], 'text': 'x'}, + {'action': 'click', 'elementId': find(snapshot, role='AXButton', label='Increment')['elementId']}, + ]) + assert error['dispatchState'] == 'not_started' and count(state()) == before + 3, error + snapshot = state() + progress, snapshot = batch(snapshot, [ + {'action': 'click', 'elementId': find(snapshot, role='AXButton', label='Show sheet')['elementId']}, + {'action': 'click', 'elementId': find(snapshot, role='AXButton', label='Increment')['elementId']}, + ]) + assert progress['completedSteps'] == 1 and 'opened or closed' in progress['error'], progress + # While the sheet is open only the sheet is observed; the step after it must not have run. + snapshot = mutate('click', snapshot, elementId=find(snapshot, role='AXButton', label='Dismiss')['elementId']) + assert count(snapshot) == before + 3, 'A batch step ran after a sheet opened' + # An editor that ignores Accessibility focus takes focus from a click in a foreground app only. + call('activate_app', bundleId=bundle) + snapshot = state() + click_field = find(snapshot, role='AXTextField', label='Fixture click-focus field') + result = call('type_text', bundleId=bundle, snapshotId=snapshot['snapshotId'], elementId=click_field['elementId'], text='clicked') + assert result['dispatched'], result + assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'clicked', 'Click-to-focus did not reach the editor' + # With text already in it and focus elsewhere, the clicked editor inserts at the end, not where the click landed. + snapshot = state() + mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text'))) + snapshot = state() + call('type_text', bundleId=bundle, snapshotId=snapshot['snapshotId'], elementId=find(snapshot, role='AXTextField', label='Fixture click-focus field')['elementId'], text=' twice') + assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'clicked twice', 'Click-to-focus typed at the click point' + # An editor whose caret cannot be confirmed at the end gets no keystrokes after its focus click. + snapshot = state() + mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text'))) + snapshot = state() + caret_field = find(snapshot, role='AXTextField', label='Fixture fixed-caret field') + before_text = caret_field.get('value') + error = refused('type_text', snapshot, elementId=caret_field['elementId'], text='X') + assert error['code'] == 'caret_unconfirmed', error + assert find(state(), role='AXTextField', label='Fixture fixed-caret field').get('value') == before_text, 'Text reached an editor whose caret was not confirmed' + # Select-all first makes the caret position moot: the click-focused editor's whole value is replaced. + snapshot = state() + mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text'))) + snapshot = state() + call('input_sequence', bundleId=bundle, snapshotId=snapshot['snapshotId'], elementId=find(snapshot, role='AXTextField', label='Fixture click-focus field')['elementId'], + steps=[{'action': 'press_key', 'key': 'Cmd+A'}, {'action': 'type_text', 'text': 'replaced'}]) + assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'replaced', 'Select-all sequence did not replace the click-focused editor' + # Move focus to the other editor, then put another app in front: the click-only editor cannot take focus. + snapshot = state() + mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text'))) + call('activate_app', bundleId='com.apple.finder') + time.sleep(0.5) + snapshot = state() + error = refused('type_text', snapshot, elementId=find(snapshot, role='AXTextField', label='Fixture click-focus field')['elementId'], text='x') + assert error['dispatchState'] == 'not_started', error + assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'replaced', 'A background app was clicked or typed into' + call('activate_app', bundleId=bundle) + print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling, text refused by a slider, typing into a date picker, a window target typing into its focused editor, a title-bar click, a batch that applies, stops at a failing step, refuses before dispatch and stops when a sheet opens, click-to-focus for an editor that ignores Accessibility focus (inserting at the end, refusing an unconfirmed caret, and none in the background); every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED')) finally: if fixture: fixture.terminate() diff --git a/apps/desktop/src/main/browser-agent/driver.test.ts b/apps/desktop/src/main/browser-agent/driver.test.ts index 7c3ce842eaa..c729da5ebbd 100644 --- a/apps/desktop/src/main/browser-agent/driver.test.ts +++ b/apps/desktop/src/main/browser-agent/driver.test.ts @@ -3223,14 +3223,18 @@ describe('credential protection', () => { const contents = await openPage() const calls: string[] = [] let probe = 0 + let firstProbeAt = Number.NaN + let snapshotAt = Number.NaN vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { if (isPageCall(expression, 'readDomQuiet')) { calls.push('probe') + if (Number.isNaN(firstProbeAt)) firstProbeAt = Date.now() const quietMs = quietReadings[Math.min(probe++, quietReadings.length - 1)] return Promise.resolve({ quietMs, loading: false }) } if (isPageCall(expression, 'collectSnapshot')) { calls.push('snapshot') + snapshotAt = Date.now() return Promise.resolve({ url: 'https://example.com/', title: 'Example', @@ -3256,6 +3260,10 @@ describe('credential protection', () => { const probesBeforeSnapshot = calls.indexOf('snapshot') expect(probesBeforeSnapshot).toBeGreaterThan(0) if (expectedProbes !== undefined) expect(probesBeforeSnapshot).toBe(expectedProbes) + else { + expect(snapshotAt - firstProbeAt).toBeGreaterThanOrEqual(1_900) + expect(snapshotAt - firstProbeAt).toBeLessThanOrEqual(2_000) + } } finally { vi.useRealTimers() } diff --git a/apps/desktop/src/main/browser-agent/page-functions.test.ts b/apps/desktop/src/main/browser-agent/page-functions.test.ts index efc9711dfa8..dc3da220e3a 100644 --- a/apps/desktop/src/main/browser-agent/page-functions.test.ts +++ b/apps/desktop/src/main/browser-agent/page-functions.test.ts @@ -381,6 +381,148 @@ describe('combobox typing surfaces', () => { expect(focusElementForTyping(0)).toMatchObject({ error: 'obstructed' }) }) + /** + * Answers hit testing with the paint stack (topmost first) over the field's sampled points and + * elsewhere, as `elementsFromPoint` does; `elementFromPoint` is its top. + */ + function stackTo( + overField: Element[], + elsewhere: (x: number, y: number) => Element[], + target: Document | ShadowRoot = document + ): void { + const stackAt = (x: number, y: number) => + x >= 10 && x <= 100 && y <= 20 ? overField : elsewhere(x, y) + Object.defineProperty(target, 'elementsFromPoint', { configurable: true, value: stackAt }) + Object.defineProperty(target, 'elementFromPoint', { + configurable: true, + value: (x: number, y: number) => stackAt(x, y)[0] ?? null, + }) + } + const page = () => [document.body, document.documentElement] + const byId = (id: string) => + document.getElementById(id) as unknown as T + + it('refuses a top-page field behind an unmarked backdrop and panel', () => { + document.body.innerHTML = ` + +
+
Subscribe to our newsletter
+ ` + const input = visible(byId('field')) + register(input) + stackTo([byId('panel'), byId('backdrop'), input, ...page()], () => [ + byId('backdrop'), + ...page(), + ]) + + expect(focusElementForTyping(0)).toMatchObject({ error: 'obstructed' }) + }) + + it('refuses a field behind an unmarked modal nested in a full-screen app shell', () => { + document.body.innerHTML = ` +
+ +
Confirm your plan
+
+ ` + const input = visible(byId('field')) + register(input) + stackTo([byId('panel'), byId('backdrop'), input, byId('shell'), ...page()], () => [ + byId('backdrop'), + byId('shell'), + ...page(), + ]) + + expect(focusElementForTyping(0)).toMatchObject({ error: 'obstructed' }) + }) + + it('refuses a field behind an unmarked modal inside the same shadow root', () => { + document.body.innerHTML = '
' + const shadow = byId('host').attachShadow({ mode: 'open' }) + shadow.innerHTML = + '
Sign up
' + const input = visible(shadow.querySelector('input') as HTMLInputElement) + const backdrop = shadow.querySelector('#backdrop') as HTMLDivElement + const panel = shadow.querySelector('#panel') as HTMLDivElement + register(input) + stackTo( + [panel, backdrop, input, byId('host'), ...page()], + () => [backdrop, byId('host'), ...page()], + shadow + ) + + expect(focusElementForTyping(0)).toMatchObject({ error: 'obstructed' }) + }) + + it('types under covers that do not reach every corner of the viewport', () => { + document.body.innerHTML = ` +
Map
+ + +
Saved to your list
+ + ` + const input = visible(byId('field')) + register(input) + const topHalf = (y: number) => y < window.innerHeight / 2 + for (const [cover, label, elsewhere] of [ + // a fixed header over a full-screen fixed map the field also sits above + [ + 'header', + 'Menu', + (_x: number, y: number): Element[] => [ + ...(topHalf(y) ? [byId('header')] : []), + byId('map'), + ...page(), + ], + ], + // a fixed header with a cookie banner pinned to the bottom corners + [ + 'header', + 'Menu', + (_x: number, y: number): Element[] => [ + topHalf(y) ? byId('header') : byId('banner'), + ...page(), + ], + ], + // a toast whose full-screen wrapper lets the pointer through, so the wrapper is in no stack + ['toast', 'Saved to your list', (): Element[] => page()], + ] as const) { + stackTo([byId(cover), input, byId('map'), ...page()], elsewhere) + expect(focusElementForTyping(0)).toMatchObject({ focused: true, coveredBy: label }) + } + }) + + it('types inside a full-screen fixed app shell that also holds the field', () => { + document.body.innerHTML = ` +
+
Shop for your home
+
+ ` + const input = visible(byId('field')) + register(input) + // the shell paints below the field, or above it when the field sits at a negative z-index + for (const overField of [ + [byId('card'), input, byId('shell'), ...page()], + [byId('card'), byId('shell'), input, ...page()], + ]) { + stackTo(overField, () => [byId('shell'), ...page()]) + expect(focusElementForTyping(0)).toMatchObject({ + focused: true, + coveredBy: 'Shop for your home', + }) + } + }) + + it('refuses a top-page field when no sampled point hits anything', () => { + document.body.innerHTML = '' + const input = visible(document.querySelector('input') as HTMLInputElement) + register(input) + Object.defineProperty(document, 'elementFromPoint', { configurable: true, value: () => null }) + + expect(focusElementForTyping(0)).toMatchObject({ error: 'obstructed' }) + }) + it('still refuses a covered field inside a frame', () => { const frame = document.createElement('iframe') document.body.append(frame) diff --git a/apps/desktop/src/main/browser-agent/page-functions.ts b/apps/desktop/src/main/browser-agent/page-functions.ts index 4e726c82ee7..e2226ded3d4 100644 --- a/apps/desktop/src/main/browser-agent/page-functions.ts +++ b/apps/desktop/src/main/browser-agent/page-functions.ts @@ -2040,11 +2040,12 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { coveredByRelatedPopup = true } } - // Top-page typing goes through keyboard focus, so a non-modal overlay does not block it; a modal - // or dialog cover still refuses, and framed fields keep the check for their mapped point. + // Top-page typing goes through keyboard focus, so a non-modal overlay does not block it. A modal + // still does: a dialog, or an unmarked backdrop, which paints above the field and under every + // corner of the viewport. Framed fields keep the check for their mapped point. const topPageField = view === window && window.top === window - const insideModal = (node: Element | null): boolean => { - for (let current = node; current; current = composedParent(current)) { + const insideDialog = (node: Element): boolean => { + for (let current: Element | null = node; current; current = composedParent(current)) { const role = current.getAttribute('role') if ( current.getAttribute('aria-modal') === 'true' || @@ -2057,12 +2058,43 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { } return false } + const behindBackdrop = (): boolean => { + const stackRoot = root as ParentNode & { + elementsFromPoint?: (x: number, y: number) => Element[] + } + if (typeof stackRoot.elementsFromPoint !== 'function') return false + const stackAt = stackRoot.elementsFromPoint.bind(stackRoot) + const aboveField = new Set() + for (const point of blockedPoints) { + for (const element of stackAt(point.x, point.y)) { + if (element === editable || editable.contains(element) || element.contains(editable)) break + aboveField.add(element) + } + } + if (aboveField.size === 0) return false + const width = editable.ownerDocument.documentElement.clientWidth || view.innerWidth + const height = editable.ownerDocument.documentElement.clientHeight || view.innerHeight + const inset = 4 + for (const [x, y] of [ + [inset, inset], + [width - inset, inset], + [inset, height - inset], + [width - inset, height - inset], + ]) { + const corner = new Set(stackAt(x, y)) + for (const element of aboveField) if (!corner.has(element)) aboveField.delete(element) + if (aboveField.size === 0) return false + } + return true + } + const hits = Array.from(new Set(blockedPoints.flatMap((point) => (point.hit ? [point.hit] : [])))) let coveredBy: string | undefined if ( !chosenPoint && topPageField && - blockedPoints.length > 0 && - !blockedPoints.some((point) => insideModal(point.hit)) + hits.length > 0 && + !hits.some(insideDialog) && + !behindBackdrop() ) { chosenPoint = { x: blockedPoints[0].x, y: blockedPoints[0].y } coveredBy = blockerLabel(firstBlocker) diff --git a/apps/desktop/src/main/computer-use/service.test.ts b/apps/desktop/src/main/computer-use/service.test.ts index 7a66b868db1..c5d463b75d1 100644 --- a/apps/desktop/src/main/computer-use/service.test.ts +++ b/apps/desktop/src/main/computer-use/service.test.ts @@ -233,6 +233,47 @@ describe('native computer use authority and lifecycle', () => { await expect(click('s2', 'chat', 'next')).resolves.toMatchObject({ dispatched: true }) }) + it.each([ + { + steps: [ + { action: 'click', elementId: 'e1' }, + { action: 'set_value', elementId: 'e1', value: 'x' }, + ], + windowId: '1', + }, + { + steps: [ + { action: 'click', elementId: 'e1' }, + { action: 'press_key', key: 'Enter' }, + ], + windowId: undefined, + }, + ])( + 'observes the one window a batch targets, else the focused window ($windowId)', + async ({ steps, windowId }) => { + const { state, service, native } = setup() + await state() + native.request.mockResolvedValueOnce({ + kind: 'action', + action: 'batch', + bundleId: 'com.example.Fixture', + dispatched: true, + verified: false, + sequence: { completedSteps: 2, totalSteps: 2 }, + }) + await service.execute('batch', 'chat', { + action: 'batch', + bundleId: 'com.example.Fixture', + snapshotId: 's1', + steps, + }) + expect(native.request).toHaveBeenLastCalledWith('get_app_state', { + bundleId: 'com.example.Fixture', + ...(windowId ? { windowId } : {}), + }) + } + ) + it('observes a partial sequence without retrying any input', async () => { const { state, service, native } = setup() await state() diff --git a/apps/desktop/src/main/computer-use/service.ts b/apps/desktop/src/main/computer-use/service.ts index afacf4696f3..397f5041589 100644 --- a/apps/desktop/src/main/computer-use/service.ts +++ b/apps/desktop/src/main/computer-use/service.ts @@ -258,18 +258,29 @@ export class ComputerUseService { } this.check(work) const owner = 'snapshotId' in input ? this.snapshots.get(input.snapshotId) : undefined - const elementId = 'elementId' in input ? input.elementId : undefined /** * Element and coordinate calls observe the window they acted on. An untargeted key keeps the * focused-window default, so a key that closes the window still returns fresh state. */ - const windowId = - ('windowId' in input ? input.windowId : undefined) ?? - (elementId - ? (owner?.elementWindows.get(elementId) ?? owner?.windowId) - : 'x' in input + const targetWindow = (target: { windowId?: string; elementId?: string; x?: number }) => + target.windowId ?? + (target.elementId + ? (owner?.elementWindows.get(target.elementId) ?? owner?.windowId) + : target.x !== undefined ? owner?.windowId : undefined) + /** A batch observes the one window all of its steps target; any untargeted step keeps the focused default. */ + const batchWindows = + input.action === 'batch' ? new Set(input.steps.map(targetWindow)) : undefined + const windowId = batchWindows + ? batchWindows.size === 1 + ? [...batchWindows][0] + : undefined + : targetWindow({ + windowId: 'windowId' in input ? input.windowId : undefined, + elementId: 'elementId' in input ? input.elementId : undefined, + x: 'x' in input ? input.x : undefined, + }) if ('snapshotId' in input) this.snapshots.delete(input.snapshotId) /** Mutations accept includeScreenshot as shorthand for observeAfter.includeScreenshot. */ const isMutation = input.action !== 'get_app_state' diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 79b15208113..190e8091440 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5800,6 +5800,17 @@ export function MondayIcon(props: SVGProps) { ) } +export function MonacoIcon(props: SVGProps) { + return ( + + + + ) +} + export function ModalIcon(props: SVGProps) { const id = useId() const leftHighlight = `modal_left_highlight_${id}` diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index a3320387b6f..50a1a16cdfe 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -195,6 +195,7 @@ import { MintlifyIcon, MistralIcon, ModalIcon, + MonacoIcon, MondayIcon, MongoDBIcon, MySQLIcon, @@ -626,6 +627,7 @@ export const blockTypeToIconMap: Record = { mistral_parse_v2: MistralIcon, mistral_parse_v3: MistralIcon, modal: ModalIcon, + monaco: MonacoIcon, monday: MondayIcon, mongodb: MongoDBIcon, mothership: Blimp, diff --git a/apps/docs/content/docs/cli/credentials.mdx b/apps/docs/content/docs/cli/credentials.mdx index 57dc0db372e..af25013d8dd 100644 --- a/apps/docs/content/docs/cli/credentials.mdx +++ b/apps/docs/content/docs/cli/credentials.mdx @@ -97,7 +97,9 @@ The printed audience is the default. To have Sim sign tokens for your own audien Step 2: add those values to keylessConfig and pass it to `sim credentials create --credentials @file`. -The proof expires after 24 hours and works only for the user who requested it, with the same providerId. If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. +The proof expires after 24 hours and works only for the user who requested it, with the same providerId. Sim holds the subject for you for 30 days until a credential saves it, so a trust configuration that takes longer to apply still works: renew the proof with `sim credentials keyless-subject --subject `. The subject stays the same, and each renewal restarts the 30 days. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. + +If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, renew the proof and retry. If it reports WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. **Examples** @@ -351,7 +353,7 @@ API request and response schemas: [deleteCredential](/api-reference/credentials/ OAuth login or personal API key required. ```bash -sim credentials keyless-subject +sim credentials keyless-subject [options] ``` **Arguments** @@ -364,6 +366,16 @@ sim credentials keyless-subject +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--subject ` | No | Renew the proof for a subject Sim issued to you earlier, instead of issuing a new one. | + + + Keyless (workload identity) credentials take two steps. Step 1: run `sim credentials keyless-subject --output json` and configure your cloud to trust the printed issuer, audience, and subject. Its requiredConfigFields lists the values your provider's keylessConfig still needs. @@ -372,7 +384,9 @@ The printed audience is the default. To have Sim sign tokens for your own audien Step 2: add those values to keylessConfig and pass it to `sim credentials create --credentials @file`. -The proof expires after 24 hours and works only for the user who requested it, with the same providerId. If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. +The proof expires after 24 hours and works only for the user who requested it, with the same providerId. Sim holds the subject for you for 30 days until a credential saves it, so a trust configuration that takes longer to apply still works: renew the proof with `sim credentials keyless-subject --subject `. The subject stays the same, and each renewal restarts the 30 days. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. + +If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, renew the proof and retry. If it reports WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. **Examples** diff --git a/apps/docs/content/docs/cli/reference.mdx b/apps/docs/content/docs/cli/reference.mdx index 6aaec498ffd..f4db68fe284 100644 --- a/apps/docs/content/docs/cli/reference.mdx +++ b/apps/docs/content/docs/cli/reference.mdx @@ -784,7 +784,9 @@ The printed audience is the default. To have Sim sign tokens for your own audien Step 2: add those values to keylessConfig and pass it to `sim credentials create --credentials @file`. -The proof expires after 24 hours and works only for the user who requested it, with the same providerId. If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. +The proof expires after 24 hours and works only for the user who requested it, with the same providerId. Sim holds the subject for you for 30 days until a credential saves it, so a trust configuration that takes longer to apply still works: renew the proof with `sim credentials keyless-subject --subject `. The subject stays the same, and each renewal restarts the 30 days. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. + +If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, renew the proof and retry. If it reports WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. **Examples** @@ -801,7 +803,7 @@ API request and response schemas: [createServiceAccountCredential](/api-referenc Issue the subject and proof for a new keyless (workload identity) credential (OAuth login or personal API key required) ```bash -sim credentials keyless-subject +sim credentials keyless-subject [options] ``` **Arguments** @@ -814,6 +816,16 @@ sim credentials keyless-subject +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--subject ` | No | Renew the proof for a subject Sim issued to you earlier, instead of issuing a new one. | + + + Keyless (workload identity) credentials take two steps. Step 1: run `sim credentials keyless-subject --output json` and configure your cloud to trust the printed issuer, audience, and subject. Its requiredConfigFields lists the values your provider's keylessConfig still needs. @@ -822,7 +834,9 @@ The printed audience is the default. To have Sim sign tokens for your own audien Step 2: add those values to keylessConfig and pass it to `sim credentials create --credentials @file`. -The proof expires after 24 hours and works only for the user who requested it, with the same providerId. If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. +The proof expires after 24 hours and works only for the user who requested it, with the same providerId. Sim holds the subject for you for 30 days until a credential saves it, so a trust configuration that takes longer to apply still works: renew the proof with `sim credentials keyless-subject --subject `. The subject stays the same, and each renewal restarts the 30 days. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. + +If creation reports WORKLOAD_IDENTITY_PROOF_EXPIRED, renew the proof and retry. If it reports WORKLOAD_IDENTITY_PROOF_INVALID, WORKLOAD_IDENTITY_SUBJECT_REQUIRED, WORKLOAD_IDENTITY_SUBJECT_IN_USE, run step 1 again, trust the new subject, and retry. **Examples** diff --git a/apps/docs/content/docs/desktop/index.mdx b/apps/docs/content/docs/desktop/index.mdx index 5fe39e511b7..b7e088961a5 100644 --- a/apps/docs/content/docs/desktop/index.mdx +++ b/apps/docs/content/docs/desktop/index.mdx @@ -102,7 +102,7 @@ Sim can then use any app except terminals and scripting apps (Script Editor, Aut Sim can read app windows and interact through clicks, text, keyboard shortcuts, scrolling, and supported accessibility controls. Screenshots and returned app content can be sent to your Sim deployment and model. Some actions require bringing the app to the foreground; app compatibility and macOS permissions can still limit what succeeds. -The conversation shows the active app and action. Select **Stop**, or press **Cmd + Shift + Escape** when that shortcut is available, to cancel active and queued computer actions. Stopping cannot undo input already delivered to an app. Verify the app's current state before repeating an interrupted action. +Stop the chat response, or press **Cmd + Shift + Escape** when that shortcut is available, to cancel active and queued computer actions. **Settings → Desktop → Computer Use** shows the action in progress. Stopping cannot undo input already delivered to an app. Verify the app's current state before repeating an interrupted action. With **Ask before using a new app** on, use **Revoke** under **Approved apps** to remove a saved approval. Signing out or changing deployments clears the device opt-in and app approvals. Computer Use never operates Sim, macOS System Settings, password managers, terminals, or scripting apps. diff --git a/apps/docs/content/docs/integrations/aws-credentials.mdx b/apps/docs/content/docs/integrations/aws-credentials.mdx index 9a050eb04a0..cd90a906d8c 100644 --- a/apps/docs/content/docs/integrations/aws-credentials.mdx +++ b/apps/docs/content/docs/integrations/aws-credentials.mdx @@ -61,12 +61,12 @@ aws iam add-client-id-to-open-id-connect-provider \ A provider holds up to 100 audiences. Once no role relies on `sts.amazonaws.com` any more, you can remove it from the provider. Reconnecting the credential keeps its audience; clearing the field returns it to the default. -The subject is unique to the credential. Reconnecting a keyless credential keeps its subject by default, so the role's trust policy keeps working; choose **Issue a new subject** only if you also update the trust policy. +The subject is unique to the credential once it is saved. Reconnecting a keyless credential keeps its subject by default, so the role's trust policy keeps working; choose **Issue a new subject** only if you also update the trust policy. ### From the API or CLI [#keyless-api-cli] -Run `sim credentials keyless-subject aws-service-account`, trust the printed subject as above, then add `roleArn` (optionally `stsRegion`, `sessionDurationSeconds`, and `audience`) to its `keylessConfig` and pass it to `sim credentials create aws-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. +Run `sim credentials keyless-subject aws-service-account`, trust the printed subject as above, then add `roleArn` (optionally `stsRegion`, `sessionDurationSeconds`, and `audience`) to its `keylessConfig` and pass it to `sim credentials create aws-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. Sim holds an unsaved subject for you for 30 days, so a trust configuration that takes longer to apply still works: reopening the connect dialog offers the same subject, and `--subject ` renews its proof from the CLI. ## Use access keys diff --git a/apps/docs/content/docs/integrations/databricks-credentials.mdx b/apps/docs/content/docs/integrations/databricks-credentials.mdx index e660c98dd94..3c22e9fc7d6 100644 --- a/apps/docs/content/docs/integrations/databricks-credentials.mdx +++ b/apps/docs/content/docs/integrations/databricks-credentials.mdx @@ -50,7 +50,7 @@ Databricks accepts any non-empty audience; Sim takes up to 255 letters, digits, ### From the API or CLI [#keyless-api-cli] -Run `sim credentials keyless-subject databricks-service-account`, trust the printed subject as above, then add `host` and `clientId` (and optionally `audience`) to its `keylessConfig` and pass it to `sim credentials create databricks-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. +Run `sim credentials keyless-subject databricks-service-account`, trust the printed subject as above, then add `host` and `clientId` (and optionally `audience`) to its `keylessConfig` and pass it to `sim credentials create databricks-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. Sim holds an unsaved subject for you for 30 days, so a trust configuration that takes longer to apply still works: reopening the connect dialog offers the same subject, and `--subject ` renews its proof from the CLI. ## Self-hosted deployments diff --git a/apps/docs/content/docs/integrations/google-service-account.mdx b/apps/docs/content/docs/integrations/google-service-account.mdx index cf6d44731fc..4c36a386c0e 100644 --- a/apps/docs/content/docs/integrations/google-service-account.mdx +++ b/apps/docs/content/docs/integrations/google-service-account.mdx @@ -247,7 +247,7 @@ Self-hosted deployments need `WORKLOAD_IDENTITY_SIGNING_KEY` set and an `https` ### From the API or CLI [#keyless-api-cli] -Run `sim credentials keyless-subject google-service-account`, trust the printed subject as above, then add `projectNumber`, `poolId`, `providerId`, and `serviceAccountEmail` (and optionally `audience`) to its `keylessConfig` and pass it to `sim credentials create google-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. For domain-wide delegation, also add `"delegation": {"user": "alice@yourcompany.com", "services": ["gmail", "google-drive"]}`, listing any of `gmail`, `google-drive`, `google-docs`, `google-sheets`, and `google-calendar`. +Run `sim credentials keyless-subject google-service-account`, trust the printed subject as above, then add `projectNumber`, `poolId`, `providerId`, and `serviceAccountEmail` (and optionally `audience`) to its `keylessConfig` and pass it to `sim credentials create google-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. Sim holds an unsaved subject for you for 30 days, so a trust configuration that takes longer to apply still works: reopening the connect dialog offers the same subject, and `--subject ` renews its proof from the CLI. For domain-wide delegation, also add `"delegation": {"user": "alice@yourcompany.com", "services": ["gmail", "google-drive"]}`, listing any of `gmail`, `google-drive`, `google-docs`, `google-sheets`, and `google-calendar`. ### Keyless domain-wide delegation [#keyless-domain-wide-delegation] diff --git a/apps/docs/content/docs/integrations/hex.mdx b/apps/docs/content/docs/integrations/hex.mdx index 1e8c22216f9..93b0087d39f 100644 --- a/apps/docs/content/docs/integrations/hex.mdx +++ b/apps/docs/content/docs/integrations/hex.mdx @@ -87,7 +87,7 @@ Create a new group in the Hex workspace, optionally with initial members. | --------- | ---- | ----------- | | `id` | string | Newly created group UUID | | `name` | string | Group name | -| `createdAt` | string | Creation timestamp | +| `createdAt` | string | Creation timestamp \(not returned by the create endpoint, so usually null\) | @@ -245,7 +245,7 @@ Get metadata and details for a specific Hex project by its ID. ### Hex Get Project Runs -Retrieve API-triggered runs for a Hex project with optional filtering by status and pagination. +Retrieve runs for a Hex project (API-triggered, scheduled, and app refresh runs) with optional filtering by status and trigger, and offset pagination. @@ -268,11 +268,12 @@ Retrieve API-triggered runs for a Hex project with optional filtering by status | ↳ `runId` | string | Run UUID | | ↳ `runUrl` | string | URL to view the run | | ↳ `status` | string | Run status \(PENDING, RUNNING, COMPLETED, ERRORED, KILLED, UNABLE_TO_ALLOCATE_KERNEL\) | +| ↳ `runTrigger` | string | How the run was triggered \(API, SCHEDULED, APP_REFRESH\) | | ↳ `startTime` | string | Run start time | | ↳ `endTime` | string | Run end time | -| ↳ `elapsedTime` | number | Elapsed time in seconds | +| ↳ `elapsedTime` | number | Elapsed run time in milliseconds | | ↳ `traceId` | string | Trace ID | -| ↳ `projectVersion` | number | Project version number | +| ↳ `projectVersion` | string | Project version that was run \("1", "2", ...; "draft" for notebook runs\) | | `total` | number | Total number of runs returned | | `traceId` | string | Top-level trace ID | | `nextPage` | string | Cursor for the next page of runs | @@ -291,7 +292,9 @@ Return the warehouse tables queried by a Hex project, including data connection | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `projectId` | string | Yes | The UUID of the Hex project | -| `limit` | number | No | Maximum number of tables to return \(1-100\) | +| `limit` | number | No | Maximum number of tables to return \(1-100, default: 25\) | +| `after` | string | No | Cursor to fetch the page of results after this value | +| `before` | string | No | Cursor to fetch the page of results before this value | #### Output @@ -302,6 +305,8 @@ Return the warehouse tables queried by a Hex project, including data connection | ↳ `dataConnectionName` | string | Data connection name | | ↳ `tableName` | string | Table name | | `total` | number | Total number of tables returned | +| `after` | string | Cursor for the next page of results | +| `before` | string | Cursor for the previous page of results | @@ -326,11 +331,12 @@ Check the status of a Hex project run by its run ID. | `runId` | string | Run UUID | | `runUrl` | string | URL to view the run | | `status` | string | Run status \(PENDING, RUNNING, COMPLETED, ERRORED, KILLED, UNABLE_TO_ALLOCATE_KERNEL\) | +| `runTrigger` | string | How the run was triggered \(API, SCHEDULED, APP_REFRESH\) | | `startTime` | string | ISO 8601 run start time | | `endTime` | string | ISO 8601 run end time | -| `elapsedTime` | number | Elapsed time in seconds | +| `elapsedTime` | number | Elapsed run time in milliseconds | | `traceId` | string | Trace ID for debugging | -| `projectVersion` | number | Project version number | +| `projectVersion` | string | Project version that was run \("1", "2", ...; "draft" for notebook runs\) | @@ -344,7 +350,7 @@ List all collections in the Hex workspace. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | number | No | Maximum number of collections to return \(1-500, default: 25\) | +| `limit` | number | No | Maximum number of collections to return \(1-100, default: 25\) | | `sortBy` | string | No | Sort by field: NAME | | `after` | string | No | Cursor to fetch the page of results after this value | | `before` | string | No | Cursor to fetch the page of results before this value | @@ -376,7 +382,7 @@ List all data connections in the Hex workspace (e.g., Snowflake, PostgreSQL, Big | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | number | No | Maximum number of connections to return \(1-500, default: 25\) | +| `limit` | number | No | Maximum number of connections to return \(1-100, default: 25\) | | `sortBy` | string | No | Sort by field: CREATED_AT or NAME | | `sortDirection` | string | No | Sort direction: ASC or DESC | | `after` | string | No | Cursor to fetch the page of results after this value | @@ -402,7 +408,7 @@ List all data connections in the Hex workspace (e.g., Snowflake, PostgreSQL, Big ### Hex List Groups -List all groups in the Hex workspace with optional sorting. +List all groups in the Hex workspace with optional sorting, or only the groups a user belongs to. @@ -413,6 +419,7 @@ List all groups in the Hex workspace with optional sorting. | `limit` | number | No | Maximum number of groups to return \(1-500, default: 25\) | | `sortBy` | string | No | Sort by field: CREATED_AT or NAME | | `sortDirection` | string | No | Sort direction: ASC or DESC | +| `userId` | string | No | Return only groups this user UUID belongs to | | `after` | string | No | Cursor to fetch the page of results after this value | | `before` | string | No | Cursor to fetch the page of results before this value | @@ -440,7 +447,7 @@ List all projects in your Hex workspace with optional filtering by status. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | number | No | Maximum number of projects to return \(1-100\) | +| `limit` | number | No | Maximum number of projects to return \(1-100, default: 25\) | | `includeArchived` | boolean | No | Include archived projects in results | | `statusFilter` | string | No | Filter by status: PUBLISHED, DRAFT, or ALL | | `includeComponents` | boolean | No | Include components in results | @@ -473,6 +480,10 @@ List all projects in your Hex workspace with optional filtering by status. | ↳ `lastPublishedAt` | string | Last published timestamp | | ↳ `createdAt` | string | Creation timestamp | | ↳ `archivedAt` | string | Archived timestamp | +| ↳ `trashedAt` | string | Trashed timestamp | +| ↳ `categories` | array | Project categories | +| ↳ `name` | string | Category name | +| ↳ `description` | string | Category description | | `total` | number | Total number of projects returned | | `after` | string | Cursor for the next page of results | | `before` | string | Cursor for the previous page of results | @@ -489,7 +500,7 @@ List all users in the Hex workspace with optional filtering and sorting. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | number | No | Maximum number of users to return \(1-100, default: 25\) | +| `limit` | number | No | Maximum number of users to return \(1-500, default: 25\) | | `sortBy` | string | No | Sort by field: NAME or EMAIL | | `sortDirection` | string | No | Sort direction: ASC or DESC | | `groupId` | string | No | Filter users by group UUID | diff --git a/apps/docs/content/docs/integrations/meta.json b/apps/docs/content/docs/integrations/meta.json index 1193d5113c4..f15021c57ae 100644 --- a/apps/docs/content/docs/integrations/meta.json +++ b/apps/docs/content/docs/integrations/meta.json @@ -185,6 +185,7 @@ "mintlify", "mistral_parse", "modal", + "monaco", "monday", "monday-service-account", "mongodb", diff --git a/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx b/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx index 92debf4e645..94222fc6e21 100644 --- a/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx +++ b/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx @@ -75,7 +75,7 @@ An app can have up to 20 federated credentials. Each Sim credential has its own ### From the API or CLI [#keyless-api-cli] -Run `sim credentials keyless-subject microsoft-app-service-account`, trust the printed subject as above, then add `tenantId` and `clientId` (optionally `environmentUrl`, `cloud` as `usgov`, `usgov_dod`, or `china`, and `audience` for a [custom audience](#custom-audience)) to its `keylessConfig` and pass it to `sim credentials create microsoft-app-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. +Run `sim credentials keyless-subject microsoft-app-service-account`, trust the printed subject as above, then add `tenantId` and `clientId` (optionally `environmentUrl`, `cloud` as `usgov`, `usgov_dod`, or `china`, and `audience` for a [custom audience](#custom-audience)) to its `keylessConfig` and pass it to `sim credentials create microsoft-app-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. Sim holds an unsaved subject for you for 30 days, so a trust configuration that takes longer to apply still works: reopening the connect dialog offers the same subject, and `--subject ` renews its proof from the CLI. ## Use a client secret or certificate diff --git a/apps/docs/content/docs/integrations/monaco.mdx b/apps/docs/content/docs/integrations/monaco.mdx new file mode 100644 index 00000000000..d54d881a338 --- /dev/null +++ b/apps/docs/content/docs/integrations/monaco.mdx @@ -0,0 +1,2101 @@ +--- +title: Monaco +description: Manage Monaco CRM records, tasks, meetings, and outreach +integration: + type: "monaco" + color: "#000000" +--- + +import { ReferenceDetails } from "@/components/ui/reference-details" + + +{/* MANUAL-CONTENT-START:intro */} +[Monaco](https://www.monaco.com/) is an AI-native revenue platform that combines a CRM, a prospect database, outbound sequencing, and a meeting recorder in one system. + +With the Monaco block, you can: + +- **Manage CRM records**: list, read, create, update, upsert, and delete contacts and accounts, and manage opportunities with their pipeline stage, estimated value, linked contacts, and custom fields +- **Track work**: create and update tasks, including email tasks with a draft, and create or list tags for contacts, accounts, and opportunities +- **Read activity**: list meetings and read their AI summaries, next actions, and transcripts, and read a contact's or account's email and LinkedIn messages and full conversation threads +- **Run outreach**: build audiences from contact IDs or filters, enroll contacts or attach audiences to campaigns, and pause, resume, enqueue, stop, or exclude sequences +- **Discover your workspace**: list pipelines and users, and read the filterable and sortable field schemas for each record type + +Create an API key in Monaco under **Settings > API Keys** (it starts with `mks_` and is shown once), then add it as a Monaco credential from the block's **Monaco Account** field. Enrolling contacts, attaching audiences to campaigns, and resuming or enqueuing sequences send real email and LinkedIn messages. + +In Sim, the Monaco block lets your agents prepare account briefs before meetings, keep the pipeline clean, turn meeting outcomes into follow-up tasks, and launch outbound to a new segment. +{/* MANUAL-CONTENT-END */} + +## Use the block [#usage-instructions] + +Connect a Monaco API key to read and write contacts, accounts, and opportunities, manage tasks and tags, read meetings, transcripts, and email or LinkedIn conversations, and run outreach by building audiences, enrolling contacts in campaigns, and controlling sequences. + +## Actions + +### Monaco List Contacts + +List Monaco contacts with optional filters, sorting, and pagination. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | +| `includeCustomFields` | boolean | No | Include each contact's `custom_field_<id>` values. Defaults to false; Get Contact always includes them. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `contacts` | array | Contacts on this page | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Get Contact + +Get one Monaco contact by ID, including scoring, heat score explanations, and custom fields. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `contactId` | string | Yes | ID of the contact to fetch | +| `includeReasoning` | boolean | No | Return each custom field as \{value, reasoning, confidence, citations\} instead of the bare value. Defaults to false. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | The contact | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `heat_score_explanation` | object | Per-signal explanations behind the heat score tier | + + + +### Monaco Create Contact + +Create a Monaco contact from an email or LinkedIn URL. Monaco enriches the contact and resolves or creates its account, which can take several seconds. A duplicate email or LinkedIn URL is rejected; use Upsert Contact to update a match instead. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `email` | string | No | Email address of the contact \(max 255 characters\). Either email or LinkedIn URL is required. | +| `linkedinUrl` | string | No | LinkedIn profile URL \(a linkedin.com/in/... URL\). Either email or LinkedIn URL is required; with only a LinkedIn URL, also pass an account ID or domain. | +| `firstName` | string | No | First name of the contact \(max 256 characters\) | +| `lastName` | string | No | Last name of the contact \(max 256 characters\) | +| `title` | string | No | Job title of the contact | +| `phoneNumber` | string | No | Phone number of the contact | +| `location` | string | No | Location of the contact \(max 256 characters\) | +| `accountId` | string | No | ID of the account to associate the contact with. Takes priority over domain, then the domain parsed from email, when resolving the account. | +| `domain` | string | No | Domain of the contact's company, used to resolve its account | +| `doNotContact` | boolean | No | Whether the contact has opted out of outreach | +| `notes` | string | No | Notes about the contact \(max 1,125,000 characters\) | +| `tags` | array | No | Tag IDs for the contact, as an array or comma-separated list. When the request matches an existing contact, replaces its existing tags. | +| `isInbound` | boolean | No | Whether the contact is an inbound lead. When true and the request creates a new contact, Monaco records it as a captured inbound lead. Ignored when the request matches an existing contact. Defaults to false. | +| `customFields` | json | No | Custom field values as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_1a2b...": "economic_buyer"\}\). Use Get Field Schemas to find the keys. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | The created contact | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `enrichment_status` | string | How enriching the contact from its identifiers ended: enriched; not_found, when no person matches them; or failed, when the lookup errored or timed out and the contact was saved with only the fields sent | + + + +### Monaco Update Contact + +Update fields on an existing Monaco contact by ID. Only the fields sent change. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `contactId` | string | Yes | ID of the contact to update | +| `firstName` | string | No | First name of the contact \(max 256 characters\) | +| `lastName` | string | No | Last name of the contact \(max 256 characters\) | +| `email` | string | No | Email address of the contact \(max 255 characters\) | +| `title` | string | No | Job title of the contact | +| `phoneNumber` | string | No | Phone number of the contact | +| `linkedinUrl` | string | No | LinkedIn profile URL \(a linkedin.com/in/... URL\) | +| `location` | string | No | Location of the contact \(max 256 characters\) | +| `notes` | string | No | Notes about the contact \(max 1,125,000 characters\) | +| `doNotContact` | boolean | No | Whether the contact has opted out of outreach | +| `tags` | array | No | Tag IDs for the contact, as an array or comma-separated list. Replaces the contact's existing tags; send \[\] to remove them all. | +| `country` | string | No | Country of the contact, as a country name or code \(max 128 characters\) | +| `jobStartDate` | string | No | Start date of the contact's current job \(YYYY-MM-DD\) | +| `clearFields` | array | No | Fields to clear on the contact, as an array or comma-separated list. Allowed: first_name, last_name, email, title, phone_number, linkedin_url, notes. | +| `customFields` | json | No | Custom field values to set, as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_<id>": "value"\}\). Custom fields are always updatable; use Get Field Schemas to find the keys. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | The updated contact | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Upsert Contact + +Create a Monaco contact or update the one matching its email (case-insensitive), then LinkedIn URL. Pass a contact ID instead to update that contact by ID, which is how to change its email. Creating a contact enriches it and can take several seconds. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `email` | string | No | Email address of the contact \(max 255 characters\). Either email or LinkedIn URL is required. | +| `linkedinUrl` | string | No | LinkedIn profile URL \(a linkedin.com/in/... URL\). Either email or LinkedIn URL is required; with only a LinkedIn URL, also pass an account ID or domain. | +| `firstName` | string | No | First name of the contact \(max 256 characters\) | +| `lastName` | string | No | Last name of the contact \(max 256 characters\) | +| `title` | string | No | Job title of the contact | +| `phoneNumber` | string | No | Phone number of the contact | +| `location` | string | No | Location of the contact \(max 256 characters\) | +| `accountId` | string | No | ID of the account to associate the contact with. Takes priority over domain, then the domain parsed from email, when resolving the account. | +| `domain` | string | No | Domain of the contact's company, used to resolve its account | +| `doNotContact` | boolean | No | Whether the contact has opted out of outreach | +| `notes` | string | No | Notes about the contact \(max 1,125,000 characters\) | +| `tags` | array | No | Tag IDs for the contact, as an array or comma-separated list. When the request matches an existing contact, replaces its existing tags. | +| `isInbound` | boolean | No | Whether the contact is an inbound lead. When true and the request creates a new contact, Monaco records it as a captured inbound lead. Ignored when the request matches an existing contact. Defaults to false. | +| `customFields` | json | No | Custom field values as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_1a2b...": "economic_buyer"\}\). Use Get Field Schemas to find the keys. | +| `id` | string | No | ID of an existing contact to update by ID instead of matching by email or LinkedIn URL; it is never created, and an unknown ID fails. Cannot be combined with account ID, domain, location, or inbound. | +| `clearFields` | array | No | Fields to clear on the contact, as an array or comma-separated list. Only applies with a contact ID. Allowed: first_name, last_name, email, title, phone_number, linkedin_url, notes. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | The created or updated contact | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `enrichment_status` | string | How enriching the contact from its identifiers ended: enriched; not_found, when no person matches them; or failed, when the lookup errored or timed out and the contact was saved with only the fields sent | + + + +### Monaco Delete Contact + +Permanently delete a Monaco contact. This cannot be undone. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `contactId` | string | Yes | ID of the contact to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | ID of the deleted contact | +| `deleted` | boolean | Whether Monaco deleted the contact | + + + +### Monaco List Accounts + +List Monaco accounts with optional filters, sorting, and pagination. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | +| `includeCustomFields` | boolean | No | Include each account's `custom_field_<id>` values. Defaults to false; Get Account always includes them. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `accounts` | array | Accounts on this page | +| ↳ `id` | string | Unique identifier for the account | +| ↳ `name` | string | Name of the account | +| ↳ `status` | string | Current status of the account | +| ↳ `owner` | object | User who owns the account | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `parent_account` | object | Account this account is a sub-account of | +| ↳ `id` | string | Unique identifier of the parent account | +| ↳ `name` | string | Name of the parent account | +| ↳ `notes` | string | Notes about the account | +| ↳ `domains` | array | The account's own domains, primary first | +| ↳ `company` | object | Company information associated with the account | +| ↳ `domain` | string | The account's primary domain: the first entry of the account's domains list | +| ↳ `linkedin_url` | string | LinkedIn company page URL | +| ↳ `description` | string | Company description | +| ↳ `employee_count` | number | Number of employees | +| ↳ `founded_year` | number | Year the company was founded | +| ↳ `industries` | array | Industry classifications | +| ↳ `total_funding_usd` | number | Total funding raised in USD | +| ↳ `last_funding_date` | string | Date of the most recent funding round | +| ↳ `company_name` | string | Company name | +| ↳ `estimated_annual_revenue_usd` | number | Estimated annual revenue in USD | +| ↳ `last_funding_round` | string | Type of the most recent funding round | +| ↳ `business_model` | string | Business model | +| ↳ `company_status` | string | Operating status of the company | +| ↳ `location` | string | Headquarters as "city, state, country" | +| ↳ `overridden_fields` | array | Fields set by a user. Company data updates don't overwrite them | +| ↳ `scoring` | object | Account scoring information | +| ↳ `tier` | string | Firmographic tier | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the account | +| ↳ `created_at` | string | When the account was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the account was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the most recent activity \(inbound or outbound\) across the account's contacts happened | +| ↳ `last_activity_type` | string | Type of the account's most recent contact activity | +| ↳ `last_contacted_at` | string | When the most recent outbound interaction with the account's contacts happened | +| ↳ `last_contacted_type` | string | Type of the most recent outbound interaction with the account's contacts | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the account's contacts happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the account's contacts | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Get Account + +Get one Monaco account by ID, including enriched company information, scoring, heat score explanations, and custom fields. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accountId` | string | Yes | ID of the account to fetch | +| `includeReasoning` | boolean | No | Return each custom field as \{value, reasoning, confidence, citations\} instead of the bare value. Defaults to false. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `account` | object | The account | +| ↳ `id` | string | Unique identifier for the account | +| ↳ `name` | string | Name of the account | +| ↳ `status` | string | Current status of the account | +| ↳ `owner` | object | User who owns the account | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `parent_account` | object | Account this account is a sub-account of | +| ↳ `id` | string | Unique identifier of the parent account | +| ↳ `name` | string | Name of the parent account | +| ↳ `notes` | string | Notes about the account | +| ↳ `domains` | array | The account's own domains, primary first | +| ↳ `company` | object | Company information associated with the account | +| ↳ `domain` | string | The account's primary domain: the first entry of the account's domains list | +| ↳ `linkedin_url` | string | LinkedIn company page URL | +| ↳ `description` | string | Company description | +| ↳ `employee_count` | number | Number of employees | +| ↳ `founded_year` | number | Year the company was founded | +| ↳ `industries` | array | Industry classifications | +| ↳ `total_funding_usd` | number | Total funding raised in USD | +| ↳ `last_funding_date` | string | Date of the most recent funding round | +| ↳ `company_name` | string | Company name | +| ↳ `estimated_annual_revenue_usd` | number | Estimated annual revenue in USD | +| ↳ `last_funding_round` | string | Type of the most recent funding round | +| ↳ `business_model` | string | Business model | +| ↳ `company_status` | string | Operating status of the company | +| ↳ `location` | string | Headquarters as "city, state, country" | +| ↳ `overridden_fields` | array | Fields set by a user. Company data updates don't overwrite them | +| ↳ `scoring` | object | Account scoring information | +| ↳ `tier` | string | Firmographic tier | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the account | +| ↳ `created_at` | string | When the account was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the account was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the most recent activity \(inbound or outbound\) across the account's contacts happened | +| ↳ `last_activity_type` | string | Type of the account's most recent contact activity | +| ↳ `last_contacted_at` | string | When the most recent outbound interaction with the account's contacts happened | +| ↳ `last_contacted_type` | string | Type of the most recent outbound interaction with the account's contacts | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the account's contacts happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the account's contacts | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `heat_score_explanation` | object | Per-signal explanations behind the heat score tier | + + + +### Monaco Upsert Account + +Create a Monaco account or update the one whose domain exactly matches the domain lookup. Pass an account ID instead to update that account by ID, or only a name to create an account without a domain where the workspace allows it. Creating an account enriches it with firmographic data and can take several seconds. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `domainLookup` | string | No | Domain used to find the account, or to create it if none matches \(a company record for the domain must exist in Monaco\). Omit it to create an account from the name alone, where the workspace allows accounts without a company; every such call creates a new account. Not allowed with an account ID. | +| `id` | string | No | ID of an existing account to update by ID instead of matching by domain lookup; it is never created, and an unknown ID fails. Requires a user-scoped API key. | +| `name` | string | No | Display name for the account \(max 256 characters\) | +| `status` | string | No | Account status \(max 64 characters\). Suggested values: NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED; any string is accepted. | +| `notes` | string | No | Notes about the account \(max 1,125,000 characters\) | +| `owner` | string | No | User ID to assign as account owner | +| `parentAccount` | string | No | Account ID to set as this account's parent. The parent must be a top-level account in the same workspace. | +| `companyId` | string | No | ID of the company to link to the account | +| `replaceEditedFields` | boolean | No | Also replace fields a user has edited, including edited domains, when linking a company. Defaults to false. | +| `linkedinUrl` | string | No | LinkedIn company page URL for matching or creating the account | +| `description` | string | No | Company description \(max 8192 characters\) | +| `employeeCount` | number | No | Number of employees \(0 or more\) | +| `foundedYear` | number | No | Year the company was founded | +| `estimatedAnnualRevenueUsd` | number | No | Estimated annual revenue in USD \(0 or more\) | +| `totalFundingUsd` | number | No | Total funding raised in USD \(0 or more\) | +| `lastFundingRound` | string | No | Type of the most recent funding round. One of: pre_seed, seed, angel, series_a, series_b, series_c, series_d, series_e, series_f, series_g, series_h, series_i, series_j, series_unknown, private_equity, debt_financing, convertible_note, grant, non_equity_assistance, corporate_round, equity_crowdfunding, product_crowdfunding, initial_coin_offering, secondary_market, post_ipo_equity, post_ipo_debt, post_ipo_secondary, undisclosed. | +| `lastFundingDate` | string | No | Date of the most recent funding round \(YYYY-MM-DD\) | +| `industries` | array | No | Industries from Monaco's standard list \(each max 128 characters\), as an array or comma-separated list. Replaces the current list. | +| `businessModel` | string | No | Business model. One of: B2B, B2C. | +| `companyStatus` | string | No | Operating status of the company. One of: active, acquired, out_of_business, ipo, unknown. | +| `location` | string | No | Headquarters as "city, state, country" | +| `tags` | array | No | Tag IDs to associate with the account, as an array or comma-separated list. Appended to the account's existing tags. | +| `domains` | array | No | The account's full ordered list of domains, primary first, as an array or comma-separated list \(max 500\). Only applies with an account ID. Send \[\] to remove them all. | +| `resetFields` | array | No | Company fields to reset to the linked company's value, as an array or comma-separated list. Only applies with an account ID. Allowed: linkedin_url, description, employee_count, founded_year, estimated_annual_revenue_usd, total_funding_usd, last_funding_round, last_funding_date, industries, business_model, company_status, location, domains. | +| `clearFields` | array | No | Fields to clear on the account, as an array or comma-separated list. Only applies with an account ID; list company_id to unlink the company. Allowed: name, notes, owner, parent_account, company_id, linkedin_url, description, employee_count, founded_year, estimated_annual_revenue_usd, total_funding_usd, last_funding_round, last_funding_date, industries, business_model, company_status, location. | +| `customFields` | json | No | Custom field values as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_7a1c...": "high"\}\). Use Get Field Schemas to find the keys. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `account` | object | The created or updated account | +| ↳ `id` | string | Unique identifier for the account | +| ↳ `name` | string | Name of the account | +| ↳ `status` | string | Current status of the account | +| ↳ `owner` | object | User who owns the account | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `parent_account` | object | Account this account is a sub-account of | +| ↳ `id` | string | Unique identifier of the parent account | +| ↳ `name` | string | Name of the parent account | +| ↳ `notes` | string | Notes about the account | +| ↳ `domains` | array | The account's own domains, primary first | +| ↳ `company` | object | Company information associated with the account | +| ↳ `domain` | string | The account's primary domain: the first entry of the account's domains list | +| ↳ `linkedin_url` | string | LinkedIn company page URL | +| ↳ `description` | string | Company description | +| ↳ `employee_count` | number | Number of employees | +| ↳ `founded_year` | number | Year the company was founded | +| ↳ `industries` | array | Industry classifications | +| ↳ `total_funding_usd` | number | Total funding raised in USD | +| ↳ `last_funding_date` | string | Date of the most recent funding round | +| ↳ `company_name` | string | Company name | +| ↳ `estimated_annual_revenue_usd` | number | Estimated annual revenue in USD | +| ↳ `last_funding_round` | string | Type of the most recent funding round | +| ↳ `business_model` | string | Business model | +| ↳ `company_status` | string | Operating status of the company | +| ↳ `location` | string | Headquarters as "city, state, country" | +| ↳ `overridden_fields` | array | Fields set by a user. Company data updates don't overwrite them | +| ↳ `scoring` | object | Account scoring information | +| ↳ `tier` | string | Firmographic tier | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the account | +| ↳ `created_at` | string | When the account was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the account was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the most recent activity \(inbound or outbound\) across the account's contacts happened | +| ↳ `last_activity_type` | string | Type of the account's most recent contact activity | +| ↳ `last_contacted_at` | string | When the most recent outbound interaction with the account's contacts happened | +| ↳ `last_contacted_type` | string | Type of the most recent outbound interaction with the account's contacts | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the account's contacts happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the account's contacts | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `skipped_domains` | array | Company websites not added because another account uses them | +| ↳ `domain` | string | The domain that was rejected | +| ↳ `reason` | string | Why it was rejected: conflict \(another account uses it\), invalid \(not a domain\), consumer \(a consumer email domain such as gmail.com\) or own_org \(your own organization domain\) | +| ↳ `account_id` | string | For a conflict, the ID of the account that already uses the domain | +| ↳ `account_name` | string | For a conflict, the name of the account that already uses the domain | + + + +### Monaco Update Account + +Update fields on an existing Monaco account by ID. Only the fields sent change. Domains replace the account's whole domain list, and a domain another account already uses fails the request. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accountId` | string | Yes | ID of the account to update | +| `name` | string | No | Display name for the account \(max 256 characters\) | +| `status` | string | No | Account status \(max 64 characters\). Suggested values: NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED; any string is accepted. | +| `notes` | string | No | Notes about the account \(max 1,125,000 characters\) | +| `owner` | string | No | User ID to assign as account owner | +| `parentAccount` | string | No | Account ID to set as this account's parent. The parent must be a top-level account in the same workspace. | +| `companyId` | string | No | ID of the company to link to the account | +| `replaceEditedFields` | boolean | No | Also replace fields a user has edited, including edited domains, when linking a company. Defaults to false. | +| `linkedinUrl` | string | No | LinkedIn company page URL | +| `description` | string | No | Company description \(max 8192 characters\) | +| `employeeCount` | number | No | Number of employees \(0 or more\) | +| `foundedYear` | number | No | Year the company was founded | +| `estimatedAnnualRevenueUsd` | number | No | Estimated annual revenue in USD \(0 or more\) | +| `totalFundingUsd` | number | No | Total funding raised in USD \(0 or more\) | +| `lastFundingRound` | string | No | Type of the most recent funding round. One of: pre_seed, seed, angel, series_a, series_b, series_c, series_d, series_e, series_f, series_g, series_h, series_i, series_j, series_unknown, private_equity, debt_financing, convertible_note, grant, non_equity_assistance, corporate_round, equity_crowdfunding, product_crowdfunding, initial_coin_offering, secondary_market, post_ipo_equity, post_ipo_debt, post_ipo_secondary, undisclosed. | +| `lastFundingDate` | string | No | Date of the most recent funding round \(YYYY-MM-DD\) | +| `industries` | array | No | Industries from Monaco's standard list \(each max 128 characters\), as an array or comma-separated list. Replaces the current list. | +| `businessModel` | string | No | Business model. One of: B2B, B2C. | +| `companyStatus` | string | No | Operating status of the company. One of: active, acquired, out_of_business, ipo, unknown. | +| `location` | string | No | Headquarters as "city, state, country" | +| `tags` | array | No | Tag IDs for the account, as an array or comma-separated list. Replaces the account's existing tags; send \[\] to remove them all. | +| `domains` | array | No | The account's complete list of domains, primary first, as an array or comma-separated list \(max 500\). Replaces the whole list: domains left out are removed. Send \[\] to remove them all. | +| `resetFields` | array | No | Company fields to reset to the linked company's value, as an array or comma-separated list; later company updates fill them again. Allowed: linkedin_url, description, employee_count, founded_year, estimated_annual_revenue_usd, total_funding_usd, last_funding_round, last_funding_date, industries, business_model, company_status, location, domains. | +| `clearFields` | array | No | Fields to clear on the account, as an array or comma-separated list. Clearing parent_account makes the account top-level and clearing company_id unlinks its company. Allowed: name, notes, owner, parent_account, company_id, linkedin_url, description, employee_count, founded_year, estimated_annual_revenue_usd, total_funding_usd, last_funding_round, last_funding_date, industries, business_model, company_status, location. | +| `customFields` | json | No | Custom field values to set, as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_<id>": "value"\}\). Custom fields are always updatable; use Get Field Schemas to find the keys. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `account` | object | The updated account | +| ↳ `id` | string | Unique identifier for the account | +| ↳ `name` | string | Name of the account | +| ↳ `status` | string | Current status of the account | +| ↳ `owner` | object | User who owns the account | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `parent_account` | object | Account this account is a sub-account of | +| ↳ `id` | string | Unique identifier of the parent account | +| ↳ `name` | string | Name of the parent account | +| ↳ `notes` | string | Notes about the account | +| ↳ `domains` | array | The account's own domains, primary first | +| ↳ `company` | object | Company information associated with the account | +| ↳ `domain` | string | The account's primary domain: the first entry of the account's domains list | +| ↳ `linkedin_url` | string | LinkedIn company page URL | +| ↳ `description` | string | Company description | +| ↳ `employee_count` | number | Number of employees | +| ↳ `founded_year` | number | Year the company was founded | +| ↳ `industries` | array | Industry classifications | +| ↳ `total_funding_usd` | number | Total funding raised in USD | +| ↳ `last_funding_date` | string | Date of the most recent funding round | +| ↳ `company_name` | string | Company name | +| ↳ `estimated_annual_revenue_usd` | number | Estimated annual revenue in USD | +| ↳ `last_funding_round` | string | Type of the most recent funding round | +| ↳ `business_model` | string | Business model | +| ↳ `company_status` | string | Operating status of the company | +| ↳ `location` | string | Headquarters as "city, state, country" | +| ↳ `overridden_fields` | array | Fields set by a user. Company data updates don't overwrite them | +| ↳ `scoring` | object | Account scoring information | +| ↳ `tier` | string | Firmographic tier | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the account | +| ↳ `created_at` | string | When the account was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the account was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the most recent activity \(inbound or outbound\) across the account's contacts happened | +| ↳ `last_activity_type` | string | Type of the account's most recent contact activity | +| ↳ `last_contacted_at` | string | When the most recent outbound interaction with the account's contacts happened | +| ↳ `last_contacted_type` | string | Type of the most recent outbound interaction with the account's contacts | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the account's contacts happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the account's contacts | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | +| ↳ `skipped_domains` | array | Company websites not added because another account uses them | +| ↳ `domain` | string | The domain that was rejected | +| ↳ `reason` | string | Why it was rejected: conflict \(another account uses it\), invalid \(not a domain\), consumer \(a consumer email domain such as gmail.com\) or own_org \(your own organization domain\) | +| ↳ `account_id` | string | For a conflict, the ID of the account that already uses the domain | +| ↳ `account_name` | string | For a conflict, the name of the account that already uses the domain | + + + +### Monaco Delete Account + +Permanently delete a Monaco account. This cannot be undone. Its contacts and opportunities are not deleted but may become unlinked. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accountId` | string | Yes | ID of the account to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | ID of the deleted account | +| `deleted` | boolean | Whether Monaco deleted the account | + + + +### Monaco List Opportunities + +List Monaco opportunities with optional filters, sorting, pagination, and a pipeline scope. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | +| `pipelineId` | string | No | Return only opportunities in this pipeline \(see List Pipelines\). If omitted, opportunities in every pipeline are returned. Stage keys in a `stage` filter are this pipeline's, or the default pipeline's when omitted; a `stage` filter also takes stage IDs. | +| `includeCustomFields` | boolean | No | Include each opportunity's `custom_field_<id>` values. Defaults to true; set false for leaner responses. Get Opportunity always includes them. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `opportunities` | array | Opportunities on this page | +| ↳ `id` | string | Unique identifier for the opportunity | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `name` | string | Name of the opportunity | +| ↳ `owner` | object | User who owns the opportunity | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `stage` | string | Current pipeline stage key of the opportunity | +| ↳ `stage_id` | string | ID of the opportunity's stage within its pipeline | +| ↳ `pipeline_id` | string | ID of the pipeline the opportunity is in | +| ↳ `pipeline_name` | string | Name of the pipeline the opportunity is in | +| ↳ `estimated_value` | object | Estimated monetary value of the opportunity | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `amount` | number | Deprecated: prefer amount_precise. Whole units of the currency. | +| ↳ `amount_precise` | number | Estimated value as a decimal amount of the specified currency | +| ↳ `estimated_close_date` | string | Expected close date for the opportunity | +| ↳ `actual_close_date` | string | Actual date the opportunity was closed | +| ↳ `summary` | string | AI-generated summary of the opportunity | +| ↳ `risks` | array | Identified risks for the opportunity | +| ↳ `notes` | string | Notes about the opportunity | +| ↳ `tags` | array | Tag names associated with the opportunity | +| ↳ `contact_ids` | array | IDs of contacts linked to the opportunity | +| ↳ `last_activity_at` | string | When the last activity on the opportunity happened \(ISO 8601\) | +| ↳ `updated_at` | string | When the opportunity was last updated \(ISO 8601\) | +| ↳ `created_at` | string | When the opportunity was created \(ISO 8601\) | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Get Opportunity + +Get one Monaco opportunity by ID, including its AI-generated summary, identified risks, stage, pipeline, and custom fields. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity to fetch | +| `includeReasoning` | boolean | No | Return each custom field as \{value, reasoning, confidence, citations\} instead of the bare value. Defaults to false. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `opportunity` | object | The opportunity | +| ↳ `id` | string | Unique identifier for the opportunity | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `name` | string | Name of the opportunity | +| ↳ `owner` | object | User who owns the opportunity | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `stage` | string | Current pipeline stage key of the opportunity | +| ↳ `stage_id` | string | ID of the opportunity's stage within its pipeline | +| ↳ `pipeline_id` | string | ID of the pipeline the opportunity is in | +| ↳ `pipeline_name` | string | Name of the pipeline the opportunity is in | +| ↳ `estimated_value` | object | Estimated monetary value of the opportunity | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `amount` | number | Deprecated: prefer amount_precise. Whole units of the currency. | +| ↳ `amount_precise` | number | Estimated value as a decimal amount of the specified currency | +| ↳ `estimated_close_date` | string | Expected close date for the opportunity | +| ↳ `actual_close_date` | string | Actual date the opportunity was closed | +| ↳ `summary` | string | AI-generated summary of the opportunity | +| ↳ `risks` | array | Identified risks for the opportunity | +| ↳ `notes` | string | Notes about the opportunity | +| ↳ `tags` | array | Tag names associated with the opportunity | +| ↳ `contact_ids` | array | IDs of contacts linked to the opportunity | +| ↳ `last_activity_at` | string | When the last activity on the opportunity happened \(ISO 8601\) | +| ↳ `updated_at` | string | When the opportunity was last updated \(ISO 8601\) | +| ↳ `created_at` | string | When the opportunity was created \(ISO 8601\) | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Create Opportunity + +Create a Monaco opportunity on an existing account, optionally setting its pipeline, stage, owner, estimated value, close date, tags, and custom fields. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Name of the opportunity | +| `accountId` | string | Yes | ID of the account to associate the opportunity with | +| `pipelineId` | string | No | Pipeline to create the opportunity in \(see List Pipelines\). If omitted, the organization's default pipeline is used. | +| `stage` | string | No | Stage key for the opportunity within its pipeline \(up to 64 characters; see List Pipelines\). If omitted, the opportunity is placed in the first stage of that pipeline. | +| `stageId` | string | No | Stage ID \(from List Pipelines\), instead of a stage key; provide either stage or stageId, not both. It names the pipeline too, so a pipeline ID, if given, must be that stage's pipeline. | +| `ownerUserId` | string | No | User ID to assign as the opportunity owner | +| `amount` | number | No | Estimated value as a decimal amount of the currency \(e.g. 50000.25\). Set together with currencyCode. | +| `currencyCode` | string | No | ISO 4217 currency code of the estimated value \(e.g. USD\), up to 8 characters. Set together with amount. | +| `estimatedCloseDate` | string | No | Expected close date \(YYYY-MM-DD\) | +| `notes` | string | No | Notes about the opportunity | +| `tags` | array | No | Tag IDs to associate with the opportunity, as an array or comma-separated list | +| `customFields` | json | No | Custom field values as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_<id>": "outbound"\}\). Use Get Field Schemas to find the keys. | +| `idempotencyKey` | string | No | Client-provided UUID that prevents duplicate creation. Reusing a key returns a duplicate_opportunity error naming the opportunity it already created. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `opportunity` | object | The created opportunity | +| ↳ `id` | string | Unique identifier for the opportunity | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `name` | string | Name of the opportunity | +| ↳ `owner` | object | User who owns the opportunity | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `stage` | string | Current pipeline stage key of the opportunity | +| ↳ `stage_id` | string | ID of the opportunity's stage within its pipeline | +| ↳ `pipeline_id` | string | ID of the pipeline the opportunity is in | +| ↳ `pipeline_name` | string | Name of the pipeline the opportunity is in | +| ↳ `estimated_value` | object | Estimated monetary value of the opportunity | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `amount` | number | Deprecated: prefer amount_precise. Whole units of the currency. | +| ↳ `amount_precise` | number | Estimated value as a decimal amount of the specified currency | +| ↳ `estimated_close_date` | string | Expected close date for the opportunity | +| ↳ `actual_close_date` | string | Actual date the opportunity was closed | +| ↳ `summary` | string | AI-generated summary of the opportunity | +| ↳ `risks` | array | Identified risks for the opportunity | +| ↳ `notes` | string | Notes about the opportunity | +| ↳ `tags` | array | Tag names associated with the opportunity | +| ↳ `contact_ids` | array | IDs of contacts linked to the opportunity | +| ↳ `last_activity_at` | string | When the last activity on the opportunity happened \(ISO 8601\) | +| ↳ `updated_at` | string | When the opportunity was last updated \(ISO 8601\) | +| ↳ `created_at` | string | When the opportunity was created \(ISO 8601\) | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Update Opportunity + +Update a Monaco opportunity: rename it, change its owner, move it to another stage or pipeline, or set its estimated value, close dates, notes, or tags. Only the fields you set are changed. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity to update | +| `name` | string | No | New name of the opportunity | +| `pipelineId` | string | No | Pipeline to move the opportunity to \(see List Pipelines\). The stage key then names a stage in that pipeline; if no stage is given, an open opportunity lands on the pipeline's first active stage and a closed one on its first closed stage with the same outcome. If omitted, the opportunity stays in its pipeline. | +| `stage` | string | No | Stage key for the opportunity within its pipeline \(up to 64 characters; see List Pipelines\) | +| `stageId` | string | No | Stage ID \(from List Pipelines\), instead of a stage key; provide either stage or stageId, not both. A stage of the opportunity's own pipeline changes its stage; a stage of another pipeline moves the opportunity there. | +| `ownerUserId` | string | No | User ID to assign as the opportunity owner | +| `amount` | number | No | Estimated value as a decimal amount of the currency \(e.g. 50000.25\). Set together with currencyCode. | +| `currencyCode` | string | No | ISO 4217 currency code of the estimated value \(e.g. USD\), up to 8 characters. Set together with amount. | +| `estimatedCloseDate` | string | No | Expected close date \(YYYY-MM-DD\) | +| `actualCloseDate` | string | No | Actual date the opportunity was closed \(YYYY-MM-DD\) | +| `notes` | string | No | Notes about the opportunity | +| `tags` | array | No | Tag IDs for the opportunity, as an array or comma-separated list. Replaces the opportunity's existing tags; send \[\] to remove them all. | +| `customFields` | json | No | Custom field values to set, as a JSON object keyed by `custom_field_<id>` \(e.g. \{"custom_field_<id>": "value"\}\). Custom fields are always updatable; use Get Field Schemas to find the keys. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `opportunity` | object | The updated opportunity | +| ↳ `id` | string | Unique identifier for the opportunity | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `name` | string | Name of the opportunity | +| ↳ `owner` | object | User who owns the opportunity | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `stage` | string | Current pipeline stage key of the opportunity | +| ↳ `stage_id` | string | ID of the opportunity's stage within its pipeline | +| ↳ `pipeline_id` | string | ID of the pipeline the opportunity is in | +| ↳ `pipeline_name` | string | Name of the pipeline the opportunity is in | +| ↳ `estimated_value` | object | Estimated monetary value of the opportunity | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `amount` | number | Deprecated: prefer amount_precise. Whole units of the currency. | +| ↳ `amount_precise` | number | Estimated value as a decimal amount of the specified currency | +| ↳ `estimated_close_date` | string | Expected close date for the opportunity | +| ↳ `actual_close_date` | string | Actual date the opportunity was closed | +| ↳ `summary` | string | AI-generated summary of the opportunity | +| ↳ `risks` | array | Identified risks for the opportunity | +| ↳ `notes` | string | Notes about the opportunity | +| ↳ `tags` | array | Tag names associated with the opportunity | +| ↳ `contact_ids` | array | IDs of contacts linked to the opportunity | +| ↳ `last_activity_at` | string | When the last activity on the opportunity happened \(ISO 8601\) | +| ↳ `updated_at` | string | When the opportunity was last updated \(ISO 8601\) | +| ↳ `created_at` | string | When the opportunity was created \(ISO 8601\) | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Delete Opportunity + +Permanently delete a Monaco opportunity. This cannot be undone. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | ID of the deleted opportunity | +| `deleted` | boolean | Whether Monaco deleted the opportunity | + + + +### Monaco List Opportunity Contacts + +List the contacts linked to a Monaco opportunity, with their email, name, title, and how each link was created. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity whose linked contacts to list | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 100. Defaults to 50. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `contacts` | array | Contacts linked to the opportunity on this page | +| ↳ `contact_id` | string | ID of the linked contact | +| ↳ `source` | string | How the link was created: `manual` \(rep or API\), `ai` \(auto-detected\), or `system` | +| ↳ `created_at` | string | When the link was created \(ISO 8601\) | +| ↳ `email` | string | Email of the linked contact | +| ↳ `first_name` | string | First name of the linked contact | +| ↳ `last_name` | string | Last name of the linked contact | +| ↳ `title` | string | Job title of the linked contact | +| ↳ `account_id` | string | Account ID of the linked contact | + + + +### Monaco Link Opportunity Contacts + +Link one or more contacts to a Monaco opportunity. Contacts that are already linked are left as they are, and previously unlinked contacts are restored. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity to link contacts to | +| `contactIds` | array | Yes | IDs of the contacts to link, as an array or comma-separated list. At most 500 IDs. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `opportunityId` | string | ID of the opportunity the contacts were linked to | +| `contactIds` | array | IDs of the contacts Monaco processed | + + + +### Monaco Unlink Opportunity Contacts + +Unlink one or more contacts from a Monaco opportunity. Contacts that are not currently linked are recorded as removed so they stay off the opportunity. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `opportunityId` | string | Yes | ID of the opportunity to unlink contacts from | +| `contactIds` | array | Yes | IDs of the contacts to unlink, as an array or comma-separated list. At most 500 IDs. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `opportunityId` | string | ID of the opportunity the contacts were unlinked from | +| `contactIds` | array | IDs of the contacts Monaco processed | + + + +### Monaco List Pipelines + +List the organization's Monaco opportunity pipelines, default first, each with its stages in display order. Use the stage keys and IDs to set an opportunity's stage. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelines` | array | Opportunity pipelines, default first | +| ↳ `id` | string | Unique identifier of the pipeline | +| ↳ `name` | string | Name of the pipeline | +| ↳ `is_default` | boolean | Whether opportunities created without a pipeline ID go to this pipeline | +| ↳ `stages` | array | The pipeline's stages in display order | +| ↳ `id` | string | Unique identifier of the stage, as written to an opportunity's `stage_id` | +| ↳ `key` | string | Stage key, as written to an opportunity's `stage` | +| ↳ `display_name` | string | Display name of the stage | + + + +### Monaco List Tasks + +List Monaco tasks with optional filters, sorting, and pagination. Email drafts are omitted from list results; use Get Task for them. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `tasks` | array | Tasks on this page | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `opportunity_id` | string | ID of the associated opportunity | +| ↳ `title` | string | Title of the task | +| ↳ `description` | string | Detailed description of the task | +| ↳ `task_type` | string | Type of task \(e.g. GENERAL_ACTION_ITEM, EMAIL\) | +| ↳ `assignee` | object | User assigned to the task | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `status` | string | Current status of the task \(TODO, DONE, or CANCELLED\) | +| ↳ `due_at` | string | When the task is due \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `created_at` | string | When the task was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the task was last updated \(ISO 8601\) | +| ↳ `message_draft` | object | Draft email contents. Populated on single-task, create, and update responses for EMAIL tasks; always null in list responses. | +| ↳ `recipients` | array | Primary recipients \(To\) of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `cc` | array | Carbon-copy recipients of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `subject` | string | Subject line of the email | +| ↳ `body` | string | Body of the email \(plain text or HTML\) | +| ↳ `sent_at` | string | When the email was sent \(ISO 8601\) | + + + +### Monaco Get Task + +Get one Monaco task by ID, including the email draft for EMAIL tasks. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `taskId` | string | Yes | ID \(UUID\) of the task to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `opportunity_id` | string | ID of the associated opportunity | +| ↳ `title` | string | Title of the task | +| ↳ `description` | string | Detailed description of the task | +| ↳ `task_type` | string | Type of task \(e.g. GENERAL_ACTION_ITEM, EMAIL\) | +| ↳ `assignee` | object | User assigned to the task | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `status` | string | Current status of the task \(TODO, DONE, or CANCELLED\) | +| ↳ `due_at` | string | When the task is due \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `created_at` | string | When the task was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the task was last updated \(ISO 8601\) | +| ↳ `message_draft` | object | Draft email contents. Populated on single-task, create, and update responses for EMAIL tasks; always null in list responses. | +| ↳ `recipients` | array | Primary recipients \(To\) of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `cc` | array | Carbon-copy recipients of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `subject` | string | Subject line of the email | +| ↳ `body` | string | Body of the email \(plain text or HTML\) | +| ↳ `sent_at` | string | When the email was sent \(ISO 8601\) | + + + +### Monaco Create Task + +Create a Monaco task, optionally linked to an account and/or opportunity. Without an assignee, the task is assigned to the API key owner. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `title` | string | Yes | Title of the task \(max 256 characters\) | +| `description` | string | No | Detailed description of the task \(max 65,536 characters\) | +| `accountId` | string | No | ID \(UUID\) of the account to associate the task with | +| `opportunityId` | string | No | ID \(UUID\) of the opportunity to associate the task with | +| `taskType` | string | No | Type of task. One of GENERAL_ACTION_ITEM, EMAIL. Defaults to GENERAL_ACTION_ITEM. | +| `assigneeId` | string | No | User ID \(UUID\) to assign the task to. Defaults to the API key owner. Use List Users to find IDs. | +| `dueAt` | string | No | Due date and time for the task \(ISO 8601 date-time\) | +| `messageDraft` | json | No | Draft email as \{"recipients": \[\{"name", "email"\}\], "cc": \[\{"name", "email"\}\], "subject", "body"\}. recipients, subject, and body are required; body may be plain text or HTML. Only valid when taskType is EMAIL. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The created task | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `opportunity_id` | string | ID of the associated opportunity | +| ↳ `title` | string | Title of the task | +| ↳ `description` | string | Detailed description of the task | +| ↳ `task_type` | string | Type of task \(e.g. GENERAL_ACTION_ITEM, EMAIL\) | +| ↳ `assignee` | object | User assigned to the task | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `status` | string | Current status of the task \(TODO, DONE, or CANCELLED\) | +| ↳ `due_at` | string | When the task is due \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `created_at` | string | When the task was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the task was last updated \(ISO 8601\) | +| ↳ `message_draft` | object | Draft email contents. Populated on single-task, create, and update responses for EMAIL tasks; always null in list responses. | +| ↳ `recipients` | array | Primary recipients \(To\) of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `cc` | array | Carbon-copy recipients of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `subject` | string | Subject line of the email | +| ↳ `body` | string | Body of the email \(plain text or HTML\) | +| ↳ `sent_at` | string | When the email was sent \(ISO 8601\) | + + + +### Monaco Update Task + +Update a Monaco task. Only the fields you set are changed; setting status to DONE or CANCELLED marks it completed. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `taskId` | string | Yes | ID \(UUID\) of the task to update | +| `title` | string | No | Title of the task \(max 256 characters\) | +| `description` | string | No | Detailed description of the task \(max 65,536 characters\) | +| `status` | string | No | Status of the task. One of TODO, DONE, CANCELLED. Moving to DONE or CANCELLED sets completed_at; moving out of those states clears it. | +| `accountId` | string | No | ID \(UUID\) of the account to associate the task with. Only supported for manually created tasks. | +| `assigneeId` | string | No | User ID \(UUID\) to assign the task to. Use List Users to find IDs. | +| `dueAt` | string | No | Due date and time for the task \(ISO 8601 date-time\) | +| `messageDraft` | json | No | Draft email as \{"recipients": \[\{"name", "email"\}\], "cc": \[\{"name", "email"\}\], "subject", "body"\}; replaces the existing draft. recipients, subject, and body are required. Only valid for EMAIL tasks. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The updated task | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `opportunity_id` | string | ID of the associated opportunity | +| ↳ `title` | string | Title of the task | +| ↳ `description` | string | Detailed description of the task | +| ↳ `task_type` | string | Type of task \(e.g. GENERAL_ACTION_ITEM, EMAIL\) | +| ↳ `assignee` | object | User assigned to the task | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `status` | string | Current status of the task \(TODO, DONE, or CANCELLED\) | +| ↳ `due_at` | string | When the task is due \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `created_at` | string | When the task was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the task was last updated \(ISO 8601\) | +| ↳ `message_draft` | object | Draft email contents. Populated on single-task, create, and update responses for EMAIL tasks; always null in list responses. | +| ↳ `recipients` | array | Primary recipients \(To\) of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `cc` | array | Carbon-copy recipients of the email | +| ↳ `name` | string | Display name of the recipient | +| ↳ `email` | string | Email address of the recipient | +| ↳ `subject` | string | Subject line of the email | +| ↳ `body` | string | Body of the email \(plain text or HTML\) | +| ↳ `sent_at` | string | When the email was sent \(ISO 8601\) | + + + +### Monaco List Meetings + +List Monaco meetings with optional filters, sorting, and pagination. Use Get Meeting for the transcript, summary, and recording. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending. Defaults to "-start_time". created_at is not a sortable meeting field. | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `meetings` | array | Meetings on this page | +| ↳ `id` | string | Unique identifier for the meeting | +| ↳ `account_id` | string | Deprecated: use account_candidates. ID of the associated account | +| ↳ `opportunity_id` | string | Deprecated: use opportunity_candidates. ID of the associated open opportunity | +| ↳ `opportunity_candidates` | array | Open opportunities the meeting associates to | +| ↳ `opportunity_id` | string | ID of the open opportunity | +| ↳ `name` | string | Name of the opportunity | +| ↳ `title` | string | Meeting title from the calendar event | +| ↳ `description` | string | Meeting description | +| ↳ `start_time` | string | Meeting start time \(ISO 8601\) | +| ↳ `end_time` | string | Meeting end time \(ISO 8601\) | +| ↳ `occurred_at` | string | When the meeting actually occurred: recording start time \(ISO 8601\). Null for meetings that were never recorded, including future and declined events. | +| ↳ `meeting_platform` | string | Video conferencing platform | +| ↳ `meeting_url` | string | URL to join the meeting | +| ↳ `organizer` | object | Meeting organizer | +| ↳ `email` | string | Email address of the organizer | +| ↳ `display_name` | string | Display name of the organizer | +| ↳ `attendees` | array | Meeting attendees | +| ↳ `email` | string | Email address of the attendee | +| ↳ `display_name` | string | Display name of the attendee | +| ↳ `response_status` | string | RSVP response status | +| ↳ `next_actions` | array | AI-generated action items from the meeting | +| ↳ `created_at` | string | When the meeting was created in Monaco \(ISO 8601\) | +| ↳ `updated_at` | string | When the meeting was last updated in Monaco \(ISO 8601\) | + + + +### Monaco Get Meeting + +Get one Monaco meeting by ID, including AI pre-meeting notes, the AI summary, a presigned recording URL, and the full speaker-attributed transcript with word-level timestamps. The transcript is returned untruncated and can be very large for long meetings. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `meetingId` | string | Yes | ID \(UUID\) of the meeting to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `meeting` | object | The meeting with notes, summary, recording URL, and transcript | +| ↳ `id` | string | Unique identifier for the meeting | +| ↳ `account_id` | string | Deprecated: use account_candidates. ID of the associated account | +| ↳ `opportunity_id` | string | Deprecated: use opportunity_candidates. ID of the associated open opportunity | +| ↳ `opportunity_candidates` | array | Open opportunities the meeting associates to | +| ↳ `opportunity_id` | string | ID of the open opportunity | +| ↳ `name` | string | Name of the opportunity | +| ↳ `title` | string | Meeting title from the calendar event | +| ↳ `description` | string | Meeting description | +| ↳ `start_time` | string | Meeting start time \(ISO 8601\) | +| ↳ `end_time` | string | Meeting end time \(ISO 8601\) | +| ↳ `occurred_at` | string | When the meeting actually occurred: recording start time \(ISO 8601\). Null for meetings that were never recorded, including future and declined events. | +| ↳ `meeting_platform` | string | Video conferencing platform | +| ↳ `meeting_url` | string | URL to join the meeting | +| ↳ `organizer` | object | Meeting organizer | +| ↳ `email` | string | Email address of the organizer | +| ↳ `display_name` | string | Display name of the organizer | +| ↳ `attendees` | array | Meeting attendees | +| ↳ `email` | string | Email address of the attendee | +| ↳ `display_name` | string | Display name of the attendee | +| ↳ `response_status` | string | RSVP response status | +| ↳ `next_actions` | array | AI-generated action items from the meeting | +| ↳ `created_at` | string | When the meeting was created in Monaco \(ISO 8601\) | +| ↳ `updated_at` | string | When the meeting was last updated in Monaco \(ISO 8601\) | +| ↳ `ai_premeeting_notes` | string | AI-generated preparation notes for an upcoming meeting; null when not generated yet | +| ↳ `meeting_summary` | string | AI-generated meeting summary | +| ↳ `video_url` | string | Presigned URL for the meeting recording; null when there is no recording yet | +| ↳ `transcript` | array | Full transcript as speaker-attributed segments with word-level timestamps. Can be very large for long meetings. | +| ↳ `speaker` | string | Name of the speaker | +| ↳ `language` | string | Language code of the segment | +| ↳ `words` | array | Words in this segment | +| ↳ `text` | string | The transcribed word | +| ↳ `start_timestamp` | number | Start time in seconds | +| ↳ `end_timestamp` | number | End time in seconds | +| ↳ `transcript_source` | string | Where the transcript came from: recall \(Monaco's recorder\), granola, circleback, or custom \(imported\). Null when the meeting has no transcript. | + + + +### Monaco List Messages + +List the email and LinkedIn conversation history for a contact or account as one recency-ordered feed, one row per conversation. Pass a row's conversation_id and channel to Get Conversation for the full thread. Paginates with a cursor. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `contactId` | string | No | ID \(UUID\) of a contact to scope the feed to that contact's message history | +| `accountId` | string | No | ID \(UUID\) of an account to scope the feed to that account's message history | +| `channels` | array | No | Channels to include, as an array or comma-separated list. Each is one of email, linkedin. Omit to include all channels. | +| `cursor` | string | No | Opaque nextCursor from a previous response; omit for the first page | +| `pageSize` | number | No | Results per page, from 1 to 100. Defaults to 25. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messages` | array | Conversations on this page, most recent first | +| ↳ `conversation_id` | string | Thread or chat identifier; pass to Get Conversation with the channel | +| ↳ `channel` | string | Channel the message was sent over \(email or linkedin\) | +| ↳ `direction` | string | Message direction: inbound, outbound, internal, or unknown | +| ↳ `subject` | string | Subject line of the conversation | +| ↳ `preview` | string | Short preview of the most recent message | +| ↳ `timestamp` | string | When the most recent message was sent \(ISO 8601\) | +| ↳ `contact_id` | string | ID of the associated contact | +| ↳ `account_id` | string | ID of the associated account | +| `nextCursor` | string | Pass back as cursor to fetch the next page; null when there are no more | +| `hasMore` | boolean | Whether more pages are available | + + + +### Monaco Get Conversation + +Get the full ordered thread of one Monaco email or LinkedIn conversation, with participants and paginated messages. Bodies are plain text and truncated past 20,000 characters. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `conversationId` | string | Yes | Conversation to fetch: the email thread_id or LinkedIn chat_id \(the conversation_id from List Messages\) | +| `channel` | string | Yes | Channel of the conversation; must match conversationId. One of email, linkedin. | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 100. Defaults to 20. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `conversation` | object | The conversation with its participants and messages on this page | +| ↳ `id` | string | Unique identifier for the conversation | +| ↳ `channel` | string | Channel of the conversation \(email or linkedin\) | +| ↳ `subject` | string | Subject line of the conversation | +| ↳ `account_id` | string | Deprecated: use account_candidates. ID of the associated account | +| ↳ `messages` | array | Messages on this page, in thread order | +| ↳ `id` | string | Unique identifier for the message | +| ↳ `channel` | string | Channel the message was sent over \(email or linkedin\) | +| ↳ `sender` | object | Sender of the message | +| ↳ `name` | string | Display name of the participant | +| ↳ `email` | string | Email address of the participant | +| ↳ `linkedin_url` | string | LinkedIn profile URL of the participant | +| ↳ `contact_id` | string | ID of the associated contact | +| ↳ `company_name` | string | Company name of the participant | +| ↳ `is_current_user` | boolean | Whether this participant is the requesting user | +| ↳ `subject` | string | Subject line of the message | +| ↳ `body` | string | Plain-text body of the message \(truncated past 20,000 characters\) | +| ↳ `body_truncated` | boolean | Whether the body was truncated due to length | +| ↳ `timestamp` | string | When the message was sent \(ISO 8601\) | + + + +### Monaco List Tags + +List every Monaco tag defined for contacts, accounts, or opportunities. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `object` | string | Yes | Object type to list tags for. One of contacts, accounts, opportunities. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tags` | array | Tags defined for the object type | +| ↳ `id` | string | Unique identifier for the tag | +| ↳ `name` | string | Display name of the tag | +| ↳ `color` | string | Display color of the tag | + + + +### Monaco Create Tag + +Create a Monaco tag for contacts, accounts, or opportunities. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `object` | string | Yes | Object type the tag applies to. One of contacts, accounts, opportunities. | +| `name` | string | Yes | Display name of the tag \(max 100 characters\). Letters, numbers, and spaces only; must be unique per object type within the organization. | +| `color` | string | No | Display color of the tag. One of green, blue, red, yellow, purple, berry, peach, gray. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tag` | object | The created tag | +| ↳ `id` | string | Unique identifier for the tag | +| ↳ `name` | string | Display name of the tag | +| ↳ `color` | string | Display color of the tag | + + + +### Monaco List Users + +List the users in the Monaco organization, optionally filtered by name or email. Each user's role says whether they are a workspace ADMIN. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | No | Case-insensitive substring filter on name or email | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `users` | array | Users in the organization | +| ↳ `id` | string | Unique identifier of the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `email` | string | Email address of the user | +| ↳ `role` | string | Workspace role: ADMIN \(manages organization-wide settings and users\) or MEMBER | + + + +### Monaco Get Current User + +Get the Monaco user and organization the API key belongs to. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `currentUser` | object | The authenticated user and organization | +| ↳ `org_id` | string | Authenticated organization ID | +| ↳ `user_id` | string | Authenticated user ID | +| ↳ `user_email` | string | Authenticated user email | +| ↳ `user_first_name` | string | Authenticated user first name | +| ↳ `user_last_name` | string | Authenticated user last name | +| ↳ `org_name` | string | Authenticated organization name | + + + +### Monaco Get Field Schemas + +List the filterable and sortable fields for a Monaco entity, with each field's key, type, allowed filter operators, whether it is updatable, custom-field settings, and allowed enum values. Use it to build list filters and to interpret custom_field_<id> keys. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `entity` | string | Yes | Entity to describe. One of accounts, contacts, meetings, opportunities, sequences, tasks. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `fields` | array | Field schemas for the entity | +| ↳ `name` | string | Display name of the field | +| ↳ `key` | string | Key used for filtering and sorting | +| ↳ `type` | string | Data type: string, number, time, boolean, enum, array, date, datetime, text, uuid, or object. A multi-select custom field is array. | +| ↳ `parent` | string | Key of the parent object field this field belongs to; null for top-level fields | +| ↳ `filterable` | boolean | Whether the field can be used in filter rules | +| ↳ `sortable` | boolean | Whether the field can be used for sorting | +| ↳ `updatable` | boolean | Whether the field can be written via the update endpoint; false marks a read-only field | +| ↳ `allowed_operators` | array | Operators that can be used to filter this field | +| ↳ `custom_field_settings` | object | Settings present only for custom fields | +| ↳ `id` | string | Unique identifier of the custom field schema | +| ↳ `is_ai_auto_filled` | boolean | Whether this custom field is automatically populated by AI | +| ↳ `question` | string | For an AI auto-filled field, the question AI answers to fill it | +| ↳ `enum_field_settings` | object | Settings present only for enum/select fields. For date and datetime fields these are semantic periods \(e.g. 'previous_week'\) used with the is operator. | +| ↳ `allowed_values` | array | Valid values that can be used in filters | +| ↳ `key` | string | The value to use in filter rules | +| ↳ `display_value` | string | Human-readable label for the value | + + + +### Monaco List Sequences + +List Monaco outreach sequences with optional filters, sorting, and pagination. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `sequences` | array | Sequences on this page | +| ↳ `id` | string | Unique identifier for the sequence | +| ↳ `contact_id` | string | ID of the contact this sequence is associated with | +| ↳ `name` | string | Name of the sequence | +| ↳ `description` | string | Description of the sequence | +| ↳ `status` | string | Current status of the sequence | +| ↳ `template_id` | string | ID of the sequence template this sequence was created from | +| ↳ `created_at` | string | When the sequence was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the sequence was last updated \(ISO 8601\) | +| ↳ `last_interaction` | string | Human-readable summary of the most recent interaction on this sequence \(e.g. "Email today", "No interactions yet"\) | +| ↳ `enqueued_at` | string | When the sequence was enqueued \(ISO 8601\) | +| ↳ `started_at` | string | When the sequence was started \(ISO 8601\) | +| ↳ `responded_at` | string | When the contact responded on this sequence \(ISO 8601\) | +| ↳ `meeting_booked_at` | string | When a meeting was booked off this sequence \(ISO 8601\) | +| ↳ `sender_id` | string | ID of the user sending this sequence | +| ↳ `sender_name` | string | Full name of the user sending this sequence | +| ↳ `exclusion_info` | object | Why this sequence was excluded: the most recently triggered rules-of-engagement violation. Null unless the status is `Excluded` and a violation was recorded. | +| ↳ `rule_name` | string | Rules-of-engagement rule that was violated | +| ↳ `description` | string | Human-readable reason the sequence was excluded | +| ↳ `overridability` | string | Whether the rule can be overridden: `HARD` or `SOFT` | + + + +### Monaco Get Sequence + +Get one Monaco sequence by ID, including its ordered list of tasks. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `sequenceId` | string | Yes | ID of the sequence to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `sequence` | object | The sequence with its ordered tasks | +| ↳ `id` | string | Unique identifier for the sequence | +| ↳ `contact_id` | string | ID of the contact this sequence is associated with | +| ↳ `name` | string | Name of the sequence | +| ↳ `description` | string | Description of the sequence | +| ↳ `status` | string | Current status of the sequence | +| ↳ `template_id` | string | ID of the sequence template this sequence was created from | +| ↳ `created_at` | string | When the sequence was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the sequence was last updated \(ISO 8601\) | +| ↳ `last_interaction` | string | Human-readable summary of the most recent interaction on this sequence \(e.g. "Email today", "No interactions yet"\) | +| ↳ `enqueued_at` | string | When the sequence was enqueued \(ISO 8601\) | +| ↳ `started_at` | string | When the sequence was started \(ISO 8601\) | +| ↳ `responded_at` | string | When the contact responded on this sequence \(ISO 8601\) | +| ↳ `meeting_booked_at` | string | When a meeting was booked off this sequence \(ISO 8601\) | +| ↳ `sender_id` | string | ID of the user sending this sequence | +| ↳ `sender_name` | string | Full name of the user sending this sequence | +| ↳ `exclusion_info` | object | Why this sequence was excluded: the most recently triggered rules-of-engagement violation. Null unless the status is `Excluded` and a violation was recorded. | +| ↳ `rule_name` | string | Rules-of-engagement rule that was violated | +| ↳ `description` | string | Human-readable reason the sequence was excluded | +| ↳ `overridability` | string | Whether the rule can be overridden: `HARD` or `SOFT` | +| ↳ `tasks` | array | Ordered list of tasks in the sequence | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `sequence_id` | string | ID of the sequence this task belongs to | +| ↳ `order` | number | Position of the task within the sequence \(1-indexed\) | +| ↳ `name` | string | Display name of the task | +| ↳ `type` | string | Type of task: generic, auto_email, manual_email, auto_linkedin_connection, manual_linkedin_connection, auto_linkedin_message, manual_linkedin_message, auto_linkedin_inmail, manual_linkedin_inmail, auto_phone, manual_phone, internal_email, gifting_via_email, gifting_via_address, or complete_sequence | +| ↳ `status` | string | Current status of the task: unknown, scheduled, pending, in_progress, paused, completed, failed, cancelled, or skipped | +| ↳ `scheduled_at` | string | When the task is scheduled for \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `task_body` | string | Task message body | +| ↳ `subject` | string | Subject line, for subject-bearing channels \(email, InMail, gifting email\) | +| ↳ `outcome` | string | Observed outcome of the task, such as positive_reply, negative_reply, email_bounced, accepted, meeting_scheduled, or do_not_contact | +| ↳ `hold_reason` | string | Why this open task can't be sent right now: not_connected, inmail_not_in_plan, rate_limited, or out_of_inmail_credits. Null when nothing is holding it, including normal waits like a step delay or a pending connection request | +| ↳ `hold_until` | string | When the engine will try the task again, if the hold clears on its own | + + + +### Monaco Update Sequence + +Apply a state transition (enqueue, pause, resume, stop, or exclude) to a Monaco sequence and return the refreshed sequence with its tasks. Enqueuing or resuming a sequence lets it send real emails and LinkedIn messages to the contact; stopping ends the sequence as failed. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `sequenceId` | string | Yes | ID of the sequence to update | +| `action` | string | Yes | State transition to apply: "enqueue" \(move a suggested sequence into the queue\), "pause" \(temporarily halt execution\), "resume" \(resume a paused sequence\), "stop" \(end the sequence as failed\), or "exclude" \(remove a needs_review or staged sequence before it is queued; the sequence moves to excluded\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `sequence` | object | The refreshed sequence with its ordered tasks | +| ↳ `id` | string | Unique identifier for the sequence | +| ↳ `contact_id` | string | ID of the contact this sequence is associated with | +| ↳ `name` | string | Name of the sequence | +| ↳ `description` | string | Description of the sequence | +| ↳ `status` | string | Current status of the sequence | +| ↳ `template_id` | string | ID of the sequence template this sequence was created from | +| ↳ `created_at` | string | When the sequence was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the sequence was last updated \(ISO 8601\) | +| ↳ `last_interaction` | string | Human-readable summary of the most recent interaction on this sequence \(e.g. "Email today", "No interactions yet"\) | +| ↳ `enqueued_at` | string | When the sequence was enqueued \(ISO 8601\) | +| ↳ `started_at` | string | When the sequence was started \(ISO 8601\) | +| ↳ `responded_at` | string | When the contact responded on this sequence \(ISO 8601\) | +| ↳ `meeting_booked_at` | string | When a meeting was booked off this sequence \(ISO 8601\) | +| ↳ `sender_id` | string | ID of the user sending this sequence | +| ↳ `sender_name` | string | Full name of the user sending this sequence | +| ↳ `exclusion_info` | object | Why this sequence was excluded: the most recently triggered rules-of-engagement violation. Null unless the status is `Excluded` and a violation was recorded. | +| ↳ `rule_name` | string | Rules-of-engagement rule that was violated | +| ↳ `description` | string | Human-readable reason the sequence was excluded | +| ↳ `overridability` | string | Whether the rule can be overridden: `HARD` or `SOFT` | +| ↳ `tasks` | array | Ordered list of tasks in the sequence | +| ↳ `id` | string | Unique identifier for the task | +| ↳ `sequence_id` | string | ID of the sequence this task belongs to | +| ↳ `order` | number | Position of the task within the sequence \(1-indexed\) | +| ↳ `name` | string | Display name of the task | +| ↳ `type` | string | Type of task: generic, auto_email, manual_email, auto_linkedin_connection, manual_linkedin_connection, auto_linkedin_message, manual_linkedin_message, auto_linkedin_inmail, manual_linkedin_inmail, auto_phone, manual_phone, internal_email, gifting_via_email, gifting_via_address, or complete_sequence | +| ↳ `status` | string | Current status of the task: unknown, scheduled, pending, in_progress, paused, completed, failed, cancelled, or skipped | +| ↳ `scheduled_at` | string | When the task is scheduled for \(ISO 8601\) | +| ↳ `completed_at` | string | When the task was completed \(ISO 8601\) | +| ↳ `task_body` | string | Task message body | +| ↳ `subject` | string | Subject line, for subject-bearing channels \(email, InMail, gifting email\) | +| ↳ `outcome` | string | Observed outcome of the task, such as positive_reply, negative_reply, email_bounced, accepted, meeting_scheduled, or do_not_contact | +| ↳ `hold_reason` | string | Why this open task can't be sent right now: not_connected, inmail_not_in_plan, rate_limited, or out_of_inmail_credits. Null when nothing is holding it, including normal waits like a step delay or a pending connection request | +| ↳ `hold_until` | string | When the engine will try the task again, if the hold clears on its own | + + + +### Monaco List Audiences + +List Monaco audiences, the reusable contact lists that campaigns enroll. The default page size returns every audience in one page. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 500. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `audiences` | array | Audiences on this page | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `type` | string | Audience type | +| ↳ `status` | string | Audience status | +| ↳ `contact_count` | number | Number of contacts currently in the audience | +| ↳ `created_at` | string | When the audience was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the audience was last updated \(ISO 8601\) | + + + +### Monaco Get Audience + +Get one Monaco audience by ID. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `audienceId` | string | Yes | ID of the audience to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `audience` | object | The audience | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `type` | string | Audience type | +| ↳ `status` | string | Audience status | +| ↳ `contact_count` | number | Number of contacts currently in the audience | +| ↳ `created_at` | string | When the audience was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the audience was last updated \(ISO 8601\) | + + + +### Monaco List Audience Contacts + +List the contacts in a Monaco audience, one page at a time. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `audienceId` | string | Yes | ID of the audience whose contacts to list | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 15. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `contacts` | array | Contacts in the audience on this page | +| ↳ `id` | string | Unique identifier for the contact | +| ↳ `account_id` | string | ID of the associated account | +| ↳ `account_name` | string | Display name of the associated account: its user-set name, falling back to the company name, then the account's domain | +| ↳ `first_name` | string | First name of the contact | +| ↳ `last_name` | string | Last name of the contact | +| ↳ `email` | string | Email address of the contact | +| ↳ `title` | string | Job title of the contact | +| ↳ `phone_number` | string | Phone number of the contact | +| ↳ `linkedin_url` | string | LinkedIn profile URL | +| ↳ `location` | string | Location of the contact | +| ↳ `country` | string | Country of the contact | +| ↳ `job_start_date` | string | Start date of the contact's current job \(YYYY-MM-DD\) | +| ↳ `source` | string | Source from which the contact was added | +| ↳ `do_not_contact` | boolean | Whether the contact has opted out of outreach | +| ↳ `notes` | string | Notes about the contact | +| ↳ `scoring` | object | Contact scoring information | +| ↳ `heat_score` | string | Heat score tier | +| ↳ `tags` | array | Tag names associated with the contact | +| ↳ `created_at` | string | When the contact was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the contact was last updated \(ISO 8601\) | +| ↳ `last_activity_at` | string | When the contact's most recent activity \(inbound or outbound\) happened | +| ↳ `last_activity_type` | string | Type of the contact's most recent activity | +| ↳ `last_outbound_at` | string | When the most recent outbound interaction with the contact happened | +| ↳ `last_outbound_type` | string | Type of the most recent outbound interaction with the contact | +| ↳ `last_inbound_at` | string | When the most recent inbound interaction from the contact happened | +| ↳ `last_inbound_type` | string | Type of the most recent inbound interaction from the contact | +| ↳ `custom_fields` | json | Custom field values keyed by their `custom_field_<id>` key. Use Get Field Schemas to see what each key means. | + + + +### Monaco Create Audience + +Create a Monaco audience, optionally seeded with contacts: either an explicit list of contact IDs, or a filters query over the contact field schemas (optionally narrowed by excluded IDs or a source audience). Audiences can be shared across campaigns. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Name of the audience to create | +| `contactIds` | array | No | Contact IDs to add to the audience on creation \(up to 10,000\), as an array or comma-separated list. Provide either contactIds or filters, not both. | +| `filters` | json | No | Selects the contacts to seed the audience with by query instead of listing every ID: an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression. Valid fields, conditions, and values come from the contacts field schemas; a rule on "account_id" selects that account's contacts. At most 10,000 matching contacts \(before exclusions\). Provide either filters or contactIds, not both. An empty list is rejected because Monaco would match every contact. | +| `excludedIds` | array | No | Contact IDs to exclude from the filtered set \(up to 10,000\). Only valid together with filters. | +| `sourceAudienceId` | string | No | Restricts the contacts selected by filters to current members of this audience. Only valid together with filters. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `audience` | object | The created audience | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `type` | string | Audience type | +| ↳ `status` | string | Audience status | +| ↳ `contact_count` | number | Number of contacts currently in the audience | +| ↳ `created_at` | string | When the audience was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the audience was last updated \(ISO 8601\) | + + + +### Monaco Add Audience Contacts + +Add contacts to a Monaco audience by ID. Audiences can be attached to multiple campaigns, so adding a contact here enrolls them in every campaign that uses this audience, which can start real email and LinkedIn outreach. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `audienceId` | string | Yes | ID of the audience to add contacts to | +| `contactIds` | array | Yes | IDs of the contacts to add \(1 to 10,000\), as an array or comma-separated list | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `failures` | array | Contacts that exist but could not be added or removed, grouped by reason | +| ↳ `reason` | string | Reason code for the failure | +| ↳ `contact_ids` | array | Contact IDs that failed for this reason | +| `audienceId` | string | ID of the audience that was modified | +| `addedCount` | number | Number of contacts this call added to the audience | +| `unknownContactIds` | array | Contact IDs that do not exist in the organization and were skipped | + + + +### Monaco Remove Audience Contacts + +Remove contacts from a Monaco audience by ID. Idempotent: IDs not in the audience are ignored. Per-contact failures are returned in failures. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `audienceId` | string | Yes | ID of the audience to remove contacts from | +| `contactIds` | array | Yes | IDs of the contacts to remove \(1 to 10,000\), as an array or comma-separated list | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `failures` | array | Contacts that exist but could not be added or removed, grouped by reason | +| ↳ `reason` | string | Reason code for the failure | +| ↳ `contact_ids` | array | Contact IDs that failed for this reason | +| `audienceId` | string | ID of the audience that was modified | + + + +### Monaco List Campaigns + +List Monaco campaigns with optional filters, sorting, and pagination. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filters` | json | No | Filters as an array of \{"field", "condition", "value"\} rules \(ANDed\), or an \{"operator": "and"\|"or", "filters": \[...\]\} expression for nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use Get Field Schemas to find filterable fields. | +| `sort` | string | No | Field to sort by; prefix with "-" for descending \(e.g. "-created_at"\). Defaults to "-created_at". | +| `page` | number | No | Page number, starting at 1. Defaults to 1. | +| `pageSize` | number | No | Results per page, from 1 to 500. Defaults to 50. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pagination` | object | Pagination for the returned page | +| ↳ `page` | number | Current page number \(1-indexed\) | +| ↳ `page_size` | number | Number of results per page | +| ↳ `total_count` | number | Total number of matching records | +| ↳ `total_pages` | number | Total number of pages | +| `campaigns` | array | Campaigns on this page | +| ↳ `id` | string | Unique identifier for the campaign | +| ↳ `name` | string | Name of the campaign | +| ↳ `status` | string | Current lifecycle status of the campaign: active, paused, completed, draft, or archived | +| ↳ `prioritized` | boolean | Whether this campaign takes priority over non-prioritized campaigns | +| ↳ `end_date` | string | Date the campaign stops enrolling \(YYYY-MM-DD\) | +| ↳ `template_id` | string | ID of the template enrolled contacts are placed on | +| ↳ `template_name` | string | Title of the template | +| ↳ `audiences` | array | Audiences enrolled in this campaign | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `enrollment_status` | string | Progress of the async task enrolling this campaign's audiences: pending, running, enriching, completed, or failed | +| ↳ `created_at` | string | When the campaign was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the campaign was last updated \(ISO 8601\) | + + + +### Monaco Get Campaign + +Get one Monaco campaign by ID, including its attached audiences, sender distribution, rules of engagement, and autopilot settings. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | string | Yes | ID of the campaign to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `campaign` | object | The campaign | +| ↳ `id` | string | Unique identifier for the campaign | +| ↳ `name` | string | Name of the campaign | +| ↳ `status` | string | Current lifecycle status of the campaign: active, paused, completed, draft, or archived | +| ↳ `prioritized` | boolean | Whether this campaign takes priority over non-prioritized campaigns | +| ↳ `end_date` | string | Date the campaign stops enrolling \(YYYY-MM-DD\) | +| ↳ `template_id` | string | ID of the template enrolled contacts are placed on | +| ↳ `template_name` | string | Title of the template | +| ↳ `audiences` | array | Audiences enrolled in this campaign | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `enrollment_status` | string | Progress of the async task enrolling this campaign's audiences: pending, running, enriching, completed, or failed | +| ↳ `created_at` | string | When the campaign was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the campaign was last updated \(ISO 8601\) | +| ↳ `distribution` | object | How sends are routed across senders \(percentage or account_owner\) | +| ↳ `mode` | string | Distribution mode: percentage or account_owner | +| ↳ `sender_ids` | array | IDs of the users eligible to send on this campaign | +| ↳ `percentages` | array | Per-sender percentages summing to 100; set only in percentage mode | +| ↳ `sender_id` | string | ID of the sending user | +| ↳ `percentage` | number | Share of sends routed to this sender | +| ↳ `fallback_sender_id` | string | Sender used whenever a contact's account is unassigned or its owner is not in sender_ids; set only in account_owner mode | +| ↳ `rules_of_engagement` | object | The campaign's effective rules of engagement; null when the campaign uses the org defaults with no campaign-level config | +| ↳ `do_not_contact` | object | Skip contacts flagged do-not-contact | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `allow_sequence_stop_on_meeting_booked` | object | Stop a contact's sequence once a meeting is booked | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `contact_cooldown` | object | Cooldown before re-contacting the same contact | +| ↳ `enabled` | boolean | Whether the cooldown is enforced | +| ↳ `days` | number | Length of the cooldown window in days \(Monaco defaults to 90\) | +| ↳ `account_cooldown` | object | Cooldown before re-contacting any contact at the same account | +| ↳ `enabled` | boolean | Whether the cooldown is enforced | +| ↳ `days` | number | Length of the cooldown window in days \(Monaco defaults to 90\) | +| ↳ `max_contacts_in_sequence` | object | Cap on simultaneously-active contacts per account | +| ↳ `enabled` | boolean | Whether the cap is enforced | +| ↳ `max_contacts` | number | Maximum contacts per account allowed in sequences simultaneously \(Monaco defaults to 5\) | +| ↳ `allow_non_account_owner_campaigns` | object | Allow enrolling contacts whose account owner is not a campaign sender | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `allow_stop_and_dismiss_on_account_status` | object | Stop and dismiss sequences when an account changes to certain statuses | +| ↳ `enabled` | boolean | Whether the rule is enforced for the trigger statuses | +| ↳ `statuses` | array | Account statuses that trigger the rule \(NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED\); null inherits the org's default set | +| ↳ `exclude_sequences_on_account_status` | object | Exclude not-yet-started sequences when an account is in \(or transitions into\) certain statuses; already-active sequences are unaffected | +| ↳ `enabled` | boolean | Whether the rule is enforced for the trigger statuses | +| ↳ `statuses` | array | Account statuses that trigger the rule \(NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED\); null inherits the org's default set | +| ↳ `autopilot_mode` | string | `autopilot` when every sender auto-sends, `supervised` when none do, `hybrid` when it is a mix | +| ↳ `autopilot_settings` | object | Senders currently on autopilot and their daily send-release cap | +| ↳ `sender_rates` | array | Senders on autopilot; senders not listed need manual approval before each send | +| ↳ `sender_id` | string | ID of the sending user | +| ↳ `daily_rate_target` | number | Daily cap on auto-released sends for this sender | +| ↳ `contact_readiness_settings` | object | Checks run on each contact before its sequence starts | +| ↳ `email` | object | Email lookup and verification before the sequence starts | +| ↳ `check_on_enroll` | boolean | Whether to find and verify each contact's email on enrollment, before the sequence starts | +| ↳ `on_failure` | string | What happens when a contact's email can't be found or verified: `exclude` \(the sequence is excluded\) or `skip_channel_steps` \(its email steps are skipped\) | + + + +### Monaco Enroll Campaign Contacts + +Enroll contacts in a Monaco campaign. This sends real outreach: enrolled contacts are placed on the campaign sequence, which sends real emails and LinkedIn messages. Monaco always creates a new audience with the given name, attaches it to the campaign, and adds the contacts to it. The campaign must be active or paused. Sequences are created asynchronously, so contacts already enrolled are deduplicated later and still count as successes. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | string | Yes | ID of the campaign to enroll contacts in; it must be active or paused | +| `audienceName` | string | Yes | Name for the new audience created to hold these contacts \(1 to 255 characters\); pick something short and relevant to the enrollment. Must be unique within the organization: a duplicate name is rejected, so vary it and retry. | +| `contactIds` | array | Yes | IDs of the contacts to enroll \(1 to 10,000\), as an array or comma-separated list | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `successCount` | number | Contacts added to the campaign's new audience | +| `failureCount` | number | Contacts that could not be added \(e.g. not found in the organization\) | +| `failureReasons` | array | Failure reasons with the number of contacts affected by each | +| ↳ `code` | string | Failure reason code \(e.g. contact_has_active_sequence\) | +| ↳ `description` | string | Human-readable description of the failure reason | +| ↳ `count` | number | Number of contacts that hit this failure | + + + +### Monaco Add Campaign Audiences + +Attach audiences to a Monaco campaign, keeping its existing ones, and return the refreshed campaign. This sends real outreach: every contact in the attached audiences is enrolled asynchronously and the campaign sequence sends them real emails and LinkedIn messages. Audiences already attached are ignored, so retries are safe. + + + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | string | Yes | ID of the campaign to attach audiences to | +| `audienceIds` | array | Yes | IDs of the audiences to attach \(at least one\), as an array or comma-separated list | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `campaign` | object | The refreshed campaign with its attached audiences | +| ↳ `id` | string | Unique identifier for the campaign | +| ↳ `name` | string | Name of the campaign | +| ↳ `status` | string | Current lifecycle status of the campaign: active, paused, completed, draft, or archived | +| ↳ `prioritized` | boolean | Whether this campaign takes priority over non-prioritized campaigns | +| ↳ `end_date` | string | Date the campaign stops enrolling \(YYYY-MM-DD\) | +| ↳ `template_id` | string | ID of the template enrolled contacts are placed on | +| ↳ `template_name` | string | Title of the template | +| ↳ `audiences` | array | Audiences enrolled in this campaign | +| ↳ `id` | string | Unique identifier for the audience | +| ↳ `name` | string | Name of the audience | +| ↳ `enrollment_status` | string | Progress of the async task enrolling this campaign's audiences: pending, running, enriching, completed, or failed | +| ↳ `created_at` | string | When the campaign was created \(ISO 8601\) | +| ↳ `updated_at` | string | When the campaign was last updated \(ISO 8601\) | +| ↳ `distribution` | object | How sends are routed across senders \(percentage or account_owner\) | +| ↳ `mode` | string | Distribution mode: percentage or account_owner | +| ↳ `sender_ids` | array | IDs of the users eligible to send on this campaign | +| ↳ `percentages` | array | Per-sender percentages summing to 100; set only in percentage mode | +| ↳ `sender_id` | string | ID of the sending user | +| ↳ `percentage` | number | Share of sends routed to this sender | +| ↳ `fallback_sender_id` | string | Sender used whenever a contact's account is unassigned or its owner is not in sender_ids; set only in account_owner mode | +| ↳ `rules_of_engagement` | object | The campaign's effective rules of engagement; null when the campaign uses the org defaults with no campaign-level config | +| ↳ `do_not_contact` | object | Skip contacts flagged do-not-contact | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `allow_sequence_stop_on_meeting_booked` | object | Stop a contact's sequence once a meeting is booked | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `contact_cooldown` | object | Cooldown before re-contacting the same contact | +| ↳ `enabled` | boolean | Whether the cooldown is enforced | +| ↳ `days` | number | Length of the cooldown window in days \(Monaco defaults to 90\) | +| ↳ `account_cooldown` | object | Cooldown before re-contacting any contact at the same account | +| ↳ `enabled` | boolean | Whether the cooldown is enforced | +| ↳ `days` | number | Length of the cooldown window in days \(Monaco defaults to 90\) | +| ↳ `max_contacts_in_sequence` | object | Cap on simultaneously-active contacts per account | +| ↳ `enabled` | boolean | Whether the cap is enforced | +| ↳ `max_contacts` | number | Maximum contacts per account allowed in sequences simultaneously \(Monaco defaults to 5\) | +| ↳ `allow_non_account_owner_campaigns` | object | Allow enrolling contacts whose account owner is not a campaign sender | +| ↳ `enabled` | boolean | Whether this rule is enforced for the campaign | +| ↳ `allow_stop_and_dismiss_on_account_status` | object | Stop and dismiss sequences when an account changes to certain statuses | +| ↳ `enabled` | boolean | Whether the rule is enforced for the trigger statuses | +| ↳ `statuses` | array | Account statuses that trigger the rule \(NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED\); null inherits the org's default set | +| ↳ `exclude_sequences_on_account_status` | object | Exclude not-yet-started sequences when an account is in \(or transitions into\) certain statuses; already-active sequences are unaffected | +| ↳ `enabled` | boolean | Whether the rule is enforced for the trigger statuses | +| ↳ `statuses` | array | Account statuses that trigger the rule \(NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED\); null inherits the org's default set | +| ↳ `autopilot_mode` | string | `autopilot` when every sender auto-sends, `supervised` when none do, `hybrid` when it is a mix | +| ↳ `autopilot_settings` | object | Senders currently on autopilot and their daily send-release cap | +| ↳ `sender_rates` | array | Senders on autopilot; senders not listed need manual approval before each send | +| ↳ `sender_id` | string | ID of the sending user | +| ↳ `daily_rate_target` | number | Daily cap on auto-released sends for this sender | +| ↳ `contact_readiness_settings` | object | Checks run on each contact before its sequence starts | +| ↳ `email` | object | Email lookup and verification before the sequence starts | +| ↳ `check_on_enroll` | boolean | Whether to find and verify each contact's email on enrollment, before the sequence starts | +| ↳ `on_failure` | string | What happens when a contact's email can't be found or verified: `exclude` \(the sequence is excluded\) or `skip_channel_steps` \(its email steps are skipped\) | + + + + diff --git a/apps/docs/content/docs/integrations/snowflake-service-account.mdx b/apps/docs/content/docs/integrations/snowflake-service-account.mdx index 7e32befebfa..cd96bf2a8fc 100644 --- a/apps/docs/content/docs/integrations/snowflake-service-account.mdx +++ b/apps/docs/content/docs/integrations/snowflake-service-account.mdx @@ -58,7 +58,7 @@ Snowflake requires each issuer and subject pair to be unique in an account, and ### From the API or CLI [#keyless-api-cli] -Run `sim credentials keyless-subject snowflake-service-account`, trust the printed subject as above, then add `host` (and `audience`, if you listed one in `OIDC_AUDIENCE_LIST`) to its `keylessConfig` and pass it to `sim credentials create snowflake-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. +Run `sim credentials keyless-subject snowflake-service-account`, trust the printed subject as above, then add `host` (and `audience`, if you listed one in `OIDC_AUDIENCE_LIST`) to its `keylessConfig` and pass it to `sim credentials create snowflake-service-account`. See [the CLI credentials reference](/cli/credentials) for the full two-step example; the proof expires after 24 hours and works only for the user who requested it. Sim holds an unsaved subject for you for 30 days, so a trust configuration that takes longer to apply still works: reopening the connect dialog offers the same subject, and `--subject ` renews its proof from the CLI. ### Self-hosted deployments diff --git a/apps/docs/content/docs/integrations/stripe.mdx b/apps/docs/content/docs/integrations/stripe.mdx index 7b996eee532..89327e71ab2 100644 --- a/apps/docs/content/docs/integrations/stripe.mdx +++ b/apps/docs/content/docs/integrations/stripe.mdx @@ -473,6 +473,8 @@ List all Payment Intents | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `customer` | string | No | Filter by customer ID | | `created` | json | No | Filter by creation date \(e.g., \{"gt": 1633024800\}\) | +| `starting_after` | string | No | Next-page cursor: last Payment Intent ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first Payment Intent ID from the current page. Do not combine with starting_after. | #### Output @@ -543,6 +545,7 @@ Search for Payment Intents using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "status:'succeeded' AND currency:'usd'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -598,6 +601,7 @@ Search for Payment Intents using query syntax | `metadata` | json | Search metadata including count and has_more | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -830,6 +834,8 @@ List all customers | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `email` | string | No | Filter by email address | | `created` | json | No | Filter by creation date | +| `starting_after` | string | No | Next-page cursor: last customer ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first customer ID from the current page. Do not combine with starting_after. | #### Output @@ -891,6 +897,7 @@ Search for customers using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "email:'customer@example.com'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -937,6 +944,7 @@ Search for customers using query syntax | `metadata` | json | Search metadata | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -1116,7 +1124,7 @@ Update an existing subscription | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | string | Yes | Subscription ID \(e.g., sub_1234567890\) | -| `items` | json | No | Updated array of items with price IDs | +| `items` | json | No | Subscription items to change, e.g. \[\{"id": "si_123", "price": "price_456"\}\]. Include the existing item "id" to update or delete \(with "deleted": true\) that item; an entry without "id" adds a new item alongside the current ones. | | `cancel_at_period_end` | boolean | No | Cancel subscription at period end | | `metadata` | json | No | Updated metadata | @@ -2538,6 +2546,8 @@ List all invoices | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `customer` | string | No | Filter by customer ID | | `status` | string | No | Filter by invoice status | +| `starting_after` | string | No | Next-page cursor: last invoice ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first invoice ID from the current page. Do not combine with starting_after. | #### Output @@ -2562,6 +2572,7 @@ Search for invoices using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "customer:'cus_1234567890'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -2571,6 +2582,7 @@ Search for invoices using query syntax | `metadata` | json | Search metadata | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -2700,6 +2712,8 @@ List all charges | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `customer` | string | No | Filter by customer ID | | `created` | json | No | Filter by creation date \(e.g., \{"gt": 1633024800\}\) | +| `starting_after` | string | No | Next-page cursor: last charge ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first charge ID from the current page. Do not combine with starting_after. | #### Output @@ -2724,6 +2738,7 @@ Search for charges using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "status:'succeeded' AND currency:'usd'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -2733,6 +2748,7 @@ Search for charges using query syntax | `metadata` | json | Search metadata including count and has_more | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -2850,6 +2866,8 @@ List all products | --------- | ---- | -------- | ----------- | | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `active` | boolean | No | Filter by active status | +| `starting_after` | string | No | Next-page cursor: last product ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first product ID from the current page. Do not combine with starting_after. | #### Output @@ -2874,6 +2892,7 @@ Search for products using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "name:'shirt'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -2883,6 +2902,7 @@ Search for products using query syntax | `metadata` | json | Search metadata | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -2981,6 +3001,8 @@ List all prices | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `product` | string | No | Filter by product ID | | `active` | boolean | No | Filter by active status | +| `starting_after` | string | No | Next-page cursor: last price ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first price ID from the current page. Do not combine with starting_after. | #### Output @@ -3005,6 +3027,7 @@ Search for prices using query syntax | --------- | ---- | -------- | ----------- | | `query` | string | Yes | Search query \(e.g., "active:'true' AND currency:'usd'"\) | | `limit` | number | No | Number of results to return \(default 10, max 100\) | +| `page` | string | No | Pagination token from metadata.next_page of the previous search; omit for the first page | #### Output @@ -3014,6 +3037,7 @@ Search for prices using query syntax | `metadata` | json | Search metadata | | ↳ `count` | number | Number of items returned | | ↳ `has_more` | boolean | Whether more items exist beyond this page | +| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results | @@ -3054,6 +3078,8 @@ List all Events | `limit` | number | No | Number of results to return \(default 10, max 100\) | | `type` | string | No | Filter by event type \(e.g., payment_intent.created\) | | `created` | json | No | Filter by creation date \(e.g., \{"gt": 1633024800\}\) | +| `starting_after` | string | No | Next-page cursor: last event ID from the previous page. Do not combine with ending_before. | +| `ending_before` | string | No | Previous-page cursor: first event ID from the current page. Do not combine with starting_after. | #### Output @@ -3092,7 +3118,9 @@ Triggers when Stripe events occur (payments, subscriptions, invoices, etc.) | `id` | string | Unique identifier for the event | | `type` | string | Event type \(e.g., payment_intent.succeeded, customer.created, invoice.paid\) | | `object` | string | Always "event" | +| `account` | string | Connected account that originated the event \(Connect events only; absent or null otherwise\) | | `api_version` | string | Stripe API version used to render the event | +| `context` | string | Authentication context needed to fetch the event or related object \(null when not applicable\) | | `created` | number | Unix timestamp when the event was created | | `data` | json | Event data containing the affected Stripe object. Structure varies by event type - access via data.object for the resource \(PaymentIntent, Customer, Invoice, etc.\) | | `livemode` | boolean | Whether this event occurred in live mode \(true\) or test mode \(false\) | diff --git a/apps/docs/openapi-v2-resources.json b/apps/docs/openapi-v2-resources.json index 5077105d00a..367c869b0ad 100644 --- a/apps/docs/openapi-v2-resources.json +++ b/apps/docs/openapi-v2-resources.json @@ -3939,7 +3939,7 @@ "post": { "operationId": "createServiceAccountCredential", "summary": "Create service-account credential", - "description": "Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in `credentials`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its `keylessConfig` as `serviceAccountJson`. A refused keyless credential names its cause in `details.code`: `409` for `WORKLOAD_IDENTITY_SUBJECT_IN_USE` or `WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE`, otherwise `400`, such as `WORKLOAD_IDENTITY_PROOF_EXPIRED` (request a new subject). Requires workspace write access. A matching source returns the existing credential with `200`; creation returns `201`. Stored secrets are omitted from the response. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", + "description": "Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in `credentials`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its `keylessConfig` as `serviceAccountJson`. A refused keyless credential names its cause in `details.code`: `409` for `WORKLOAD_IDENTITY_SUBJECT_IN_USE` or `WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE`, otherwise `400`, such as `WORKLOAD_IDENTITY_PROOF_EXPIRED` (renew the proof by passing the subject to Create workload identity subject). Requires workspace write access. A matching source returns the existing credential with `200`; creation returns `201`. Stored secrets are omitted from the response. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", "x-sim-operation": "credentials.service_accounts.create", "x-oauth-scope": "api:write", "tags": ["Credentials"], @@ -4382,7 +4382,7 @@ "post": { "operationId": "createCredentialWorkloadIdentitySubject", "summary": "Create workload identity subject", - "description": "Start a keyless credential. Returns the issuer, audience, and a fresh subject to configure in the cloud's trust policy, plus `keylessConfig`: a prefilled `serviceAccountJson` whose `subjectProof` is valid for 24 hours and only for the requesting user. Add the `requiredConfigFields`, then call Create service-account credential with the same `providerId`; Sim verifies the trust while saving. The returned `audience` is the default; set `audience` in `keylessConfig` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns `400` with `details.code` `WORKLOAD_IDENTITY_NOT_CONFIGURED`. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", + "description": "Start a keyless credential. Returns the issuer, audience, and a new subject to configure in the cloud's trust policy (or, when `subject` names one Sim is holding for you, that same subject with a renewed proof), plus `keylessConfig`: a prefilled `serviceAccountJson` whose `subjectProof` is valid for 24 hours and only for the requesting user. Sim holds the subject for you until `subjectHeldUntil` (30 days, restarted by each renewal) unless a credential saves it first; pass it back as `subject` to renew the proof without changing the subject, so a trust policy that takes days to apply still matches. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. Add the `requiredConfigFields`, then call Create service-account credential with the same `providerId`; Sim verifies the trust while saving. The returned `audience` is the default; set `audience` in `keylessConfig` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns `400` with `details.code` `WORKLOAD_IDENTITY_NOT_CONFIGURED`. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", "x-sim-operation": "credentials.workload_identity.prepare", "x-oauth-scope": "api:write", "tags": ["Credentials"], @@ -4396,7 +4396,7 @@ ], "requestBody": { "required": true, - "description": "Workspace that will own the keyless credential and the service-account provider it will be created under.", + "description": "Workspace that will own the keyless credential, the service-account provider it will be created under, and optionally a held subject whose proof to renew.", "content": { "application/json": { "schema": { @@ -4409,6 +4409,14 @@ "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", "providerId": "aws-service-account" } + }, + "example2": { + "summary": "Request example 2", + "value": { + "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "providerId": "aws-service-account", + "subject": "sim:workspace::credential:" + } } } } @@ -4443,6 +4451,8 @@ "subject": "sim:workspace::credential:", "subjectProof": "", "expiresAt": "2026-06-21T14:17:11.000Z", + "subjectIssuedAt": "2026-06-20T14:17:11.000Z", + "subjectHeldUntil": "2026-07-20T14:17:11.000Z", "keylessConfig": { "authType": "oidc_role", "subject": "sim:workspace::credential:", @@ -18650,6 +18660,18 @@ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "description": "ISO 8601 timestamp after which `subjectProof` can no longer create a credential." }, + "subjectIssuedAt": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "description": "ISO 8601 timestamp when Sim first issued `subject`." + }, + "subjectHeldUntil": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "description": "ISO 8601 timestamp until which Sim holds `subject` for you if no credential saves it. Renewing the proof restarts the hold; once a credential saves the subject, it is no longer held." + }, "keylessConfig": { "type": "object", "properties": { @@ -18690,6 +18712,8 @@ "subject", "subjectProof", "expiresAt", + "subjectIssuedAt", + "subjectHeldUntil", "keylessConfig", "requiredConfigFields", "optionalConfigFields" @@ -18718,6 +18742,8 @@ "subject": "sim:workspace::credential:", "subjectProof": "", "expiresAt": "2026-06-21T14:17:11.000Z", + "subjectIssuedAt": "2026-06-20T14:17:11.000Z", + "subjectHeldUntil": "2026-07-20T14:17:11.000Z", "keylessConfig": { "authType": "oidc_role", "subject": "sim:workspace::credential:", @@ -18748,16 +18774,27 @@ "google-service-account" ], "description": "Service-account provider the keyless credential will be created under; pass the same value to Create service-account credential. The response and List credential providers name the fields its keyless configuration needs." + }, + "subject": { + "description": "A subject Sim issued to you earlier that no credential has saved yet. Renews its proof instead of issuing a new subject, so a trust configuration that already allows it keeps working. Refused with `WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING` once a credential saves it, after its hold ends, or for a subject issued to someone else.", + "type": "string", + "minLength": 1, + "maxLength": 512 } }, "required": ["workspaceId", "providerId"], "additionalProperties": false, "title": "Create workload identity subject request", - "description": "Workspace that will own the keyless credential and the service-account provider it will be created under.", + "description": "Workspace that will own the keyless credential, the service-account provider it will be created under, and optionally a held subject whose proof to renew.", "examples": [ { "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", "providerId": "aws-service-account" + }, + { + "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "providerId": "aws-service-account", + "subject": "sim:workspace::credential:" } ] }, diff --git a/apps/docs/scripts/fixtures/content-baseline.json b/apps/docs/scripts/fixtures/content-baseline.json index 374997b16ac..11ba80992de 100644 --- a/apps/docs/scripts/fixtures/content-baseline.json +++ b/apps/docs/scripts/fixtures/content-baseline.json @@ -3072,8 +3072,8 @@ { "lang": "bash", "meta": null, - "sha256": "57a7270a0eaa11ca8fe83eedaf7846ab9193ecbc2e2a1d2189f5a350d7881fdc", - "bytes": 44, + "sha256": "866eaf5e25ac2d6471b52118f082a507d5300b7c80cd82ecbe3cfbce598c5f6a", + "bytes": 54, "line": 351 }, { @@ -11197,8 +11197,8 @@ { "lang": "bash", "meta": null, - "sha256": "57a7270a0eaa11ca8fe83eedaf7846ab9193ecbc2e2a1d2189f5a350d7881fdc", - "bytes": 44, + "sha256": "866eaf5e25ac2d6471b52118f082a507d5300b7c80cd82ecbe3cfbce598c5f6a", + "bytes": 54, "line": 800 }, { diff --git a/apps/sim/app/api/credentials/workload-identity/route.ts b/apps/sim/app/api/credentials/workload-identity/route.ts index e9d22dd8a7a..ad5aed65892 100644 --- a/apps/sim/app/api/credentials/workload-identity/route.ts +++ b/apps/sim/app/api/credentials/workload-identity/route.ts @@ -14,7 +14,15 @@ export const POST = defineInternalJsonRoute({ operation: credentialOperations.prepareWorkloadIdentity, rateLimit: internalRateLimits.user({ bucketName: 'credentials.workload_identity.prepare' }), errorPolicy: internalCredentialErrorPolicy, - mapInput: ({ body }) => body, + mapInput: ({ body: { newSubject, ...body } }) => ({ + ...body, + // A reconnect shows a subject only once someone asks for a new one, so opening it holds none. + subjectRequest: newSubject + ? { kind: 'new' as const } + : body.credentialId + ? { kind: 'unheld' as const } + : { kind: 'latest' as const }, + }), useCase: prepareWorkloadIdentity, present: (result) => result, }) diff --git a/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.test.ts b/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.test.ts index 34db14a7e72..1665320363b 100644 --- a/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.test.ts +++ b/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.test.ts @@ -26,8 +26,31 @@ vi.mock('@/lib/api/server/routes/v2-api-key-auth', () => v2ApiKeyAuthModuleMock) vi.mock('@/lib/core/rate-limiter', () => v2RateLimiterModuleMock) vi.mock('@/lib/workspaces/application/context', () => workspaceContextMock) vi.mock('@sim/platform-authz/workspace', () => workspaceAuthzMock) +vi.mock('@/lib/credentials/workload-identity/pending-subjects', async () => { + const { newWorkloadIdentitySubject } = await vi.importActual< + typeof import('@/lib/credentials/workload-identity/subject') + >('@/lib/credentials/workload-identity/subject') + const actual = await vi.importActual< + typeof import('@/lib/credentials/workload-identity/pending-subjects') + >('@/lib/credentials/workload-identity/pending-subjects') + return { + ...actual, + resolvePendingWorkloadIdentitySubject: vi.fn( + async ({ workspaceId }: { workspaceId: string }) => { + const issuedAt = new Date() + return { + subject: newWorkloadIdentitySubject({ workspaceId }), + issuedAt, + heldUntil: new Date(issuedAt.getTime() + actual.PENDING_SUBJECT_HOLD_MS), + reused: false, + } + } + ), + } +}) import { env } from '@/lib/core/config/env' +import { resolvePendingWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/pending-subjects' import { verifyWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/subject' import { POST } from '@/app/api/v2/credentials/workload-identity/subjects/route' @@ -88,6 +111,8 @@ describe('POST /api/v2/credentials/workload-identity/subjects', () => { subject: expect.stringMatching(`^sim:workspace:${WORKSPACE_ID}:credential:`), subjectProof: expect.any(String), expiresAt: expect.any(String), + subjectIssuedAt: expect.any(String), + subjectHeldUntil: expect.any(String), keylessConfig: { authType: 'oidc_role', subject: data.subject, @@ -109,6 +134,16 @@ describe('POST /api/v2/credentials/workload-identity/subjects', () => { ).toThrow(expect.objectContaining({ detailCode: 'WORKLOAD_IDENTITY_PROOF_INVALID' })) }) + it('issues each call its own subject unless one is named for renewal', async () => { + await issue({ workspaceId: WORKSPACE_ID, providerId: 'aws-service-account' }) + const subject = `sim:workspace:${WORKSPACE_ID}:credential:abcdef0123456789` + await issue({ workspaceId: WORKSPACE_ID, providerId: 'aws-service-account', subject }) + + expect( + vi.mocked(resolvePendingWorkloadIdentitySubject).mock.calls.map((call) => call[1]) + ).toEqual([{ kind: 'new' }, { kind: 'renew', subject }]) + }) + it('leaves the audience out for Google Cloud, whose audience is per credential', async () => { const response = await issue({ workspaceId: WORKSPACE_ID, diff --git a/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.ts b/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.ts index 6314776157d..7a52e5d3a3c 100644 --- a/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.ts +++ b/apps/sim/app/api/v2/credentials/workload-identity/subjects/route.ts @@ -26,8 +26,9 @@ const workloadIdentitySubjectErrorPolicy = createV2ResourceConcealmentPolicy({ /** * POST /api/v2/credentials/workload-identity/subjects — Issue the subject a * cloud trust configuration pins, and the proof that attaches it to a new - * keyless credential. A deployment that cannot sign is refused exactly as - * Create service-account credential refuses a keyless configuration there. + * keyless credential, or renew the proof for a subject Sim is still holding. + * A deployment that cannot sign is refused exactly as Create service-account + * credential refuses a keyless configuration there. */ export const POST = defineV2JsonRoute({ contract: v2CreateCredentialWorkloadIdentitySubjectContract, @@ -38,12 +39,20 @@ export const POST = defineV2JsonRoute({ mapInput: ({ body }) => ({ workspaceId: body.workspaceId, provider: workloadIdentityProviderFor(body.providerId), + subjectRequest: body.subject + ? { kind: 'renew' as const, subject: body.subject } + : { kind: 'new' as const }, }), useCase: prepareWorkloadIdentity, present: (result, { body }) => { if (!result.available) { throw new WorkloadIdentityNotConfiguredError() } + // This route only issues or renews subjects Sim holds, so the hold is always reported. + const { subjectIssuedAt, subjectHeldUntil } = result + if (!subjectIssuedAt || !subjectHeldUntil) { + throw new Error('A held workload identity subject must report its hold') + } const { authType, requiredFields, optionalFields } = WORKLOAD_IDENTITY_KEYLESS_CONFIGS[workloadIdentityProviderFor(body.providerId)] return { @@ -53,6 +62,8 @@ export const POST = defineV2JsonRoute({ subject: result.subject, subjectProof: result.subjectProof, expiresAt: result.expiresAt, + subjectIssuedAt, + subjectHeldUntil, keylessConfig: { authType, subject: result.subject, subjectProof: result.subjectProof }, requiredConfigFields: [...requiredFields], optionalConfigFields: [...optionalFields], diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx index 4c852be0c0f..2eca1fc5feb 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx @@ -26,7 +26,6 @@ import { AssistantActionReviewMessage, useHasAssistantActionReview, } from '@/components/assistant-actions' -import { ComputerUseActivity } from '@/components/computer-use/activity' import { SMOOTH_CHASE_RATE } from '@/lib/core/utils/smooth-bottom-chase' import type { TurnOrigin } from '@/lib/mothership/generated/protocol' import { inter } from '@/app/_styles/fonts/inter/inter' @@ -1039,7 +1038,6 @@ export function MothershipChat({ onAnimationEnd={animateInput ? onInputAnimationEnd : undefined} >
- libDesktopMock) -const native = vi.hoisted(() => ({ - getActivity: vi.fn<() => Promise>(), - onActivity: vi.fn<(listener: (activity: ComputerUseActivity | null) => void) => () => void>(), - cancel: vi.fn<() => Promise>(), -})) let root: Root let container: HTMLDivElement let client: QueryClient @@ -36,10 +30,6 @@ beforeEach(() => { useMothershipEffortStore.getState().reset() useMothershipPlanEffortStore.getState().reset() client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) - libDesktopMockFns.mockGetDesktopBridge.mockReturnValue({ computerUse: native }) - native.getActivity.mockResolvedValue(null) - native.onActivity.mockReturnValue(() => {}) - native.cancel.mockResolvedValue(undefined) container = document.createElement('div') document.body.append(container) root = createRoot(container) @@ -49,7 +39,7 @@ afterEach(async () => { client.clear() container.remove() }) -async function render(advanced: boolean, workspaceId?: string) { +async function render(advanced: boolean) { await act(async () => root.render( @@ -63,7 +53,6 @@ async function render(advanced: boolean, workspaceId?: string) { }} > { - await render(false, workspaceId) - const listener = native.onActivity.mock.calls[0]?.[0] - expect(listener).toBeDefined() - if (!listener) throw new Error('Chat did not subscribe to native activity') - await act(async () => - listener({ - toolCallId: 'action', - scopeId: 'chat', - appName: 'Synthetic app', - action: 'type_text', - startedAt: 1, - stopShortcutAvailable: true, - }) - ) - const status = container.querySelector('[role="status"]') - expect(status?.textContent).toContain('Synthetic app · Typing') - const stop = status?.querySelector('button') - expect(stop?.getAttribute('aria-keyshortcuts')).toBe('Meta+Shift+Escape') - await act(async () => stop?.click()) - expect(native.cancel).toHaveBeenCalledOnce() - await act(async () => listener(null)) - expect(container.querySelector('[role="status"]')).toBeNull() - } -) diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/keyless-connection.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/keyless-connection.tsx index 45be6523be2..f9d71f6fefb 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/keyless-connection.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/keyless-connection.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { ChipModalError, ChipModalField } from '@sim/emcn' +import { formatDate } from '@sim/utils/formatting' import { generateShortId } from '@sim/utils/id' import { isApiClientError } from '@/lib/api/client/errors' import { resourceScopeFields, resourceScopeFromOwner } from '@/lib/core/resource-scope' @@ -24,6 +25,10 @@ import { * Databricks dialogs differ only in their own fields and trust snippet. */ +/** + * `keep`: on reconnect, the subject the credential already uses; on create, the subject + * Sim is holding from an earlier visit. `new`: a subject issued for this connection. + */ export type TrustSubjectChoice = 'keep' | 'new' interface KeylessSetupInput

{ @@ -41,7 +46,9 @@ interface KeylessSetupInput

{ * Keyless subjects bind to a workspace, so organization credentials never * offer it, and a deployment without a signing key reports it unavailable. On * reconnect the configured trust is kept by default: issuing a new subject - * would break the role or federation policy the customer already set up. + * would break the role or federation policy the customer already set up. A new + * credential likewise opens on the subject Sim is holding from an earlier visit, + * which the customer may already have trusted, and can ask for a new one instead. */ export function useKeylessSetup

({ provider, @@ -51,9 +58,8 @@ export function useKeylessSetup

({ enabled = true, }: KeylessSetupInput

) { const [sessionKey] = useState(() => generateShortId()) - const [subjectChoice, setSubjectChoice] = useState( - credentialId ? 'keep' : 'new' - ) + const isReconnect = Boolean(credentialId) + const [subjectChoice, setSubjectChoice] = useState('keep') const workspaceScoped = enabled && Boolean(workspaceId) && !organizationId const setup = useWorkloadIdentitySetup({ workspaceId, @@ -62,7 +68,21 @@ export function useKeylessSetup

({ enabled: workspaceScoped, credentialId, }) - const trust = setup.data?.available ? setup.data : undefined + const heldTrust = setup.data?.available ? setup.data : undefined + const heldFromEarlier = !isReconnect && Boolean(heldTrust?.subjectReused) + const offersSubjectChoice = isReconnect || heldFromEarlier + const choice: TrustSubjectChoice = offersSubjectChoice ? subjectChoice : 'keep' + // Its own query, so switching back to `keep` still shows the held subject rather than + // refetching the newest one, which this fetch just minted. + const freshSetup = useWorkloadIdentitySetup({ + workspaceId, + provider, + sessionKey, + enabled: workspaceScoped && offersSubjectChoice && choice === 'new', + newSubject: true, + }) + const freshTrust = freshSetup.data?.available ? freshSetup.data : undefined + const trust = choice === 'new' ? freshTrust : isReconnect ? undefined : heldTrust // Undefined until edited, so a reconnect opens on its stored audience once that loads. const [audienceInput, setAudienceInput] = useState() const audienceValue = audienceInput ?? setup.data?.reconnectAudience ?? '' @@ -78,7 +98,7 @@ export function useKeylessSetup

({ const storedAuthType = providerAuthType(provider, setup.data?.reconnectAuthType) // A keyless credential reopens on keyless only where this deployment can still sign. const reconnectAuthType = - storedAuthType === WORKLOAD_IDENTITY_KEYLESS_CONFIGS[provider].authType && !trust + storedAuthType === WORKLOAD_IDENTITY_KEYLESS_CONFIGS[provider].authType && !heldTrust ? undefined : storedAuthType return { @@ -95,11 +115,15 @@ export function useKeylessSetup

({ * lookup, and for an organization credential, whose stored secret this lookup never reads. */ reconnectFieldsLoaded: workspaceScoped && setup.isSuccess, - isReconnect: Boolean(credentialId), - subjectChoice, + isReconnect, + /** Whether the dialog offers a choice between the subject in hand and a new one. */ + offersSubjectChoice, + /** When Sim issued the held subject a new credential opened on. */ + heldSubjectIssuedAt: heldFromEarlier ? heldTrust?.subjectIssuedAt : undefined, + subjectChoice: choice, setSubjectChoice, - /** The new subject to submit, or undefined when keeping the stored one. */ - trust: subjectChoice === 'new' ? trust : undefined, + /** The subject to submit with its proof, or undefined when keeping the stored one. */ + trust, /** The audience field's value; empty keeps the default. */ audienceInput: audienceValue, setAudienceInput, @@ -112,8 +136,12 @@ export function useKeylessSetup

({ /** The audience tokens carry: the custom one, else the default; undefined while invalid. */ audience: audienceError ? undefined : (customAudience ?? defaultAudience), /** Ready to submit a keyless configuration. */ - ready: !audienceError && (subjectChoice === 'keep' ? Boolean(credentialId) : Boolean(trust)), - error: setup.isError ? setup.error : null, + ready: !audienceError && ((isReconnect && choice === 'keep') || Boolean(trust)), + error: setup.isError + ? setup.error + : choice === 'new' && freshSetup.isError + ? freshSetup.error + : null, } } @@ -161,19 +189,26 @@ export function KeylessTrustFields({ }: KeylessTrustFieldsProps) { return ( <> - {setup.isReconnect && ( + {setup.offersSubjectChoice && ( setup.setSubjectChoice(value as TrustSubjectChoice)} options={[ - { value: 'keep', label: 'Keep the subject this credential already uses' }, + { + value: 'keep', + label: setup.heldSubjectIssuedAt + ? `Use the subject issued ${formatDate(new Date(setup.heldSubjectIssuedAt))}` + : 'Keep the subject this credential already uses', + }, { value: 'new', label: 'Issue a new subject' }, ]} hint={ setup.subjectChoice === 'keep' - ? 'Your existing trust configuration keeps working.' + ? setup.isReconnect + ? 'Your existing trust configuration keeps working.' + : 'Keep this if your trust configuration already allows it.' : 'You must update your trust configuration to the new subject before saving.' } /> @@ -199,7 +234,11 @@ export function KeylessTrustFields({ title='Subject' value={setup.trust.subject} copyLabel='Copy subject' - hint='Unique to this credential. Your configuration trusts only this subject.' + hint={ + setup.trust.subjectHeldUntil + ? `Your trust configuration must allow exactly this subject. Sim holds it for you until ${formatDate(new Date(setup.trust.subjectHeldUntil))}.` + : 'Your trust configuration must allow exactly this subject.' + } /> )} diff --git a/apps/sim/blocks/blocks/azure_devops.test.ts b/apps/sim/blocks/blocks/azure_devops.test.ts new file mode 100644 index 00000000000..df28ca4d0fc --- /dev/null +++ b/apps/sim/blocks/blocks/azure_devops.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest' +import { AzureDevOpsBlock } from '@/blocks/blocks/azure_devops' +import type { BlockToolParamsContext } from '@/blocks/types' + +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => AzureDevOpsBlock.tools.config?.params?.(inputs, context) ?? {} + +/** + * On an agent call the model supplies `continuationToken`/`top`/`iterationPath`/`format` under the + * tool's own names; the per-operation fields (`pipelinesContinuationToken`, `queryTop`, …) are never + * rendered in the agent tool input. The handler forwards `{ ...inputs, ...mapped }`. + */ +describe('AzureDevOpsBlock agent tool calls', () => { + const agentCall = (operation: string, args: Record): Record => { + const inputs = { ...args, operation, organization: 'org', project: 'proj' } + return { ...inputs, ...buildParams(inputs, { operation }) } + } + + it.each(['azure_devops_list_pipelines', 'azure_devops_get_comments'])( + 'keeps the model continuationToken on %s', + (operation) => { + expect( + agentCall(operation, { workItemId: 1, continuationToken: 'ct' }).continuationToken + ).toBe('ct') + } + ) + + it('keeps the model top on azure_devops_query_work_items', () => { + expect(agentCall('azure_devops_query_work_items', { wiqlQuery: 'q', top: 5 }).top).toBe(5) + }) + + it('keeps the model iterationPath on azure_devops_update_work_item', () => { + const mapped = agentCall('azure_devops_update_work_item', { + workItemId: 1, + iterationPath: 'proj\\Sprint 2', + }) + expect(mapped.iterationPath).toBe('proj\\Sprint 2') + }) + + it('keeps the model format on azure_devops_add_comment', () => { + const mapped = agentCall('azure_devops_add_comment', { workItemId: 1, format: 'markdown' }) + expect(mapped.format).toBe('markdown') + }) + + it('drops a List Builds token left over on the canvas under list pipelines', () => { + const inputs = { operation: 'azure_devops_list_pipelines', continuationToken: 'stale' } + expect({ ...inputs, ...buildParams(inputs) }.continuationToken).toBeUndefined() + }) + + it('drops a Create Work Item iteration path left over on the canvas under update', () => { + const inputs = { + operation: 'azure_devops_update_work_item', + workItemId: 1, + iterationPath: 'stale', + } + expect({ ...inputs, ...buildParams(inputs) }.iterationPath).toBeUndefined() + }) +}) diff --git a/apps/sim/blocks/blocks/azure_devops.ts b/apps/sim/blocks/blocks/azure_devops.ts index 6ef93a310c0..f4a0a56b46c 100644 --- a/apps/sim/blocks/blocks/azure_devops.ts +++ b/apps/sim/blocks/blocks/azure_devops.ts @@ -1,7 +1,7 @@ import { AzureIcon } from '@/components/icons' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { withSavedCredential } from '@/blocks/utils' +import { toolCallArg, withSavedCredential } from '@/blocks/utils' import type { AzureDevOpsBasicWorkItemType } from '@/tools/azure_devops/types' import { AZURE_DEVOPS_BASIC_WORK_ITEM_STATES } from '@/tools/azure_devops/utils' import { getTrigger } from '@/triggers' @@ -718,7 +718,7 @@ export const AzureDevOpsBlock: BlockConfig = { ], config: { tool: (params) => params.operation as string, - params: (params) => { + params: (params, context) => { const base = { accessToken: params.accessToken as string, organization: params.organization as string, @@ -729,7 +729,10 @@ export const AzureDevOpsBlock: BlockConfig = { return { ...base, top: resolveTop(params.pipelinesTop, params.top), - continuationToken: (params.pipelinesContinuationToken as string) || undefined, + continuationToken: + (params.pipelinesContinuationToken as string) || + toolCallArg(context, params.continuationToken) || + undefined, } case 'azure_devops_get_pipeline': return { ...base, pipelineId: Number(params.pipelineId) } @@ -774,7 +777,7 @@ export const AzureDevOpsBlock: BlockConfig = { return { ...base, wiqlQuery: params.wiqlQuery as string, - top: params.queryTop ? Number(params.queryTop) : undefined, + top: resolveTop(params.queryTop, toolCallArg(context, params.top)), } case 'azure_devops_get_work_item': return { ...base, workItemId: Number(params.workItemId) } @@ -814,7 +817,10 @@ export const AzureDevOpsBlock: BlockConfig = { completedWork: params.completedWork ? Number(params.completedWork) : undefined, description: (params.description as string) || undefined, areaPath: (params.areaPath as string) || undefined, - iterationPath: (params.updateIterationPath as string) || undefined, + iterationPath: + (params.updateIterationPath as string) || + toolCallArg(context, params.iterationPath) || + undefined, tags: (params.tags as string) || undefined, } case 'azure_devops_add_comment': @@ -822,14 +828,20 @@ export const AzureDevOpsBlock: BlockConfig = { ...base, workItemId: Number(params.workItemId), text: params.commentText as string, - format: (params.commentFormat as string) || undefined, + format: + (params.commentFormat as string) || + toolCallArg(context, params.format) || + undefined, } case 'azure_devops_get_comments': return { ...base, workItemId: Number(params.workItemId), top: resolveTop(params.commentsTop, params.top), - continuationToken: (params.commentsContinuationToken as string) || undefined, + continuationToken: + (params.commentsContinuationToken as string) || + toolCallArg(context, params.continuationToken) || + undefined, } default: return base diff --git a/apps/sim/blocks/blocks/gmail.test.ts b/apps/sim/blocks/blocks/gmail.test.ts new file mode 100644 index 00000000000..6f999b9c3e6 --- /dev/null +++ b/apps/sim/blocks/blocks/gmail.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest' +import { GmailV2Block } from '@/blocks/blocks/gmail' +import type { BlockToolParamsContext } from '@/blocks/types' + +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => GmailV2Block.tools.config?.params?.(inputs, context) ?? {} + +/** + * On an agent call the model supplies `threadId`/`format`/`name`/`query`/`labelIds` under the + * tool's own names; the dedicated V2 fields (`targetThreadId`, `threadFormat`, `labelName`, + * `listQuery`, `threadLabelIds`) are never rendered in the agent tool input. + */ +describe('GmailV2Block agent tool calls', () => { + const agentCall = (operation: string, args: Record): Record => { + const inputs = { ...args, operation } + return { ...inputs, ...buildParams(inputs, { operation }) } + } + + it('keeps the model threadId and format on get_thread_gmail', () => { + const mapped = agentCall('get_thread_gmail', { threadId: 't-1', format: 'minimal' }) + expect(mapped.threadId).toBe('t-1') + expect(mapped.format).toBe('minimal') + }) + + it.each(['create_label_gmail', 'update_label_gmail'])( + 'keeps the model name on %s', + (operation) => { + expect(agentCall(operation, { labelId: 'l-1', name: 'Receipts' }).name).toBe('Receipts') + } + ) + + it.each(['list_drafts_gmail', 'list_threads_gmail'])( + 'keeps the model query and labelIds on %s', + (operation) => { + const mapped = agentCall(operation, { query: 'is:unread', labelIds: 'INBOX' }) + expect(mapped.query).toBe('is:unread') + expect(mapped.labelIds).toBe('INBOX') + } + ) + + it('drops a Send threadId left over on the canvas under get_thread_gmail', () => { + const inputs = { operation: 'get_thread_gmail', threadId: 'stale' } + expect({ ...inputs, ...buildParams(inputs) }.threadId).toBeUndefined() + }) +}) diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index 924929e8fb0..2f120055b2a 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -5,8 +5,10 @@ import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' import { createVersionedToolSelector, + isToolCall, normalizeFileInput, SERVICE_ACCOUNT_SUBBLOCKS, + toolCallArg, } from '@/blocks/utils' import { getTrigger } from '@/triggers' @@ -969,8 +971,8 @@ export const GmailV2Block: BlockConfig = { suffix: '_v2', fallbackToolId: 'gmail_send_v2', }), - params: (params) => { - const mapped = GmailBlock.tools?.config?.params?.(params) ?? params + params: (params, context) => { + const mapped = GmailBlock.tools?.config?.params?.(params, context) ?? params const { labelName, targetThreadId, @@ -982,17 +984,18 @@ export const GmailV2Block: BlockConfig = { ...rest } = mapped if (LABEL_WRITE_OPERATIONS.includes(rest.operation)) { - rest.name = labelName || undefined + rest.name = labelName || toolCallArg(context, rest.name) || undefined rest.messageListVisibility = messageListVisibility || undefined rest.labelListVisibility = labelListVisibility || undefined } if (THREAD_TARGET_OPERATIONS.includes(rest.operation)) { - rest.threadId = targetThreadId - rest.format = threadFormat || undefined + rest.threadId = isToolCall(context) ? targetThreadId || rest.threadId : targetThreadId + rest.format = threadFormat || toolCallArg(context, rest.format) || undefined } if (LIST_OPERATIONS.includes(rest.operation)) { - rest.query = listQuery || undefined - rest.labelIds = threadLabelIds ? String(threadLabelIds).trim() : undefined + rest.query = listQuery || toolCallArg(context, rest.query) || undefined + const labelIds = threadLabelIds || toolCallArg(context, rest.labelIds) + rest.labelIds = labelIds ? String(labelIds).trim() : undefined } return rest }, diff --git a/apps/sim/blocks/blocks/google_calendar.test.ts b/apps/sim/blocks/blocks/google_calendar.test.ts new file mode 100644 index 00000000000..07beac3b4d8 --- /dev/null +++ b/apps/sim/blocks/blocks/google_calendar.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' +import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar' +import type { BlockToolParamsContext } from '@/blocks/types' + +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => GoogleCalendarBlock.tools.config?.params?.(inputs, context) ?? {} + +/** + * On an agent call the model supplies `timeZone` under the tool's own name; the dedicated + * `listTimeZone` field is never rendered in the agent tool input. + */ +describe('GoogleCalendarBlock list timeZone', () => { + it('keeps the model timeZone on an agent list call', () => { + const inputs = { operation: 'list', timeZone: 'America/New_York' } + const mapped = buildParams(inputs, { operation: 'list' }) + expect({ ...inputs, ...mapped }.timeZone).toBe('America/New_York') + }) + + it('drops a Create/Update timeZone left over on the canvas under list', () => { + const inputs = { operation: 'list', timeZone: 'America/New_York' } + expect({ ...inputs, ...buildParams(inputs) }.timeZone).toBeUndefined() + }) +}) diff --git a/apps/sim/blocks/blocks/google_calendar.ts b/apps/sim/blocks/blocks/google_calendar.ts index c3ff09602b3..a514ef29a54 100644 --- a/apps/sim/blocks/blocks/google_calendar.ts +++ b/apps/sim/blocks/blocks/google_calendar.ts @@ -2,7 +2,7 @@ import { GoogleCalendarIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { createVersionedToolSelector, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils' +import { createVersionedToolSelector, SERVICE_ACCOUNT_SUBBLOCKS, toolCallArg } from '@/blocks/utils' import { getTrigger } from '@/triggers' const CALENDAR_FIELD = ['calendarId', 'manualCalendarId'] as const @@ -991,7 +991,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, throw new Error(`Invalid Google Calendar operation: ${params.operation}`) } }, - params: (params) => { + params: (params, context) => { const { oauthCredential, operation, @@ -1075,7 +1075,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, processedParams.timeZone = typeof listTimeZone === 'string' && listTimeZone.trim() ? listTimeZone.trim() - : undefined + : toolCallArg(context, processedParams.timeZone) } return { diff --git a/apps/sim/blocks/blocks/google_drive.test.ts b/apps/sim/blocks/blocks/google_drive.test.ts index b3b10a362e7..51168d39875 100644 --- a/apps/sim/blocks/blocks/google_drive.test.ts +++ b/apps/sim/blocks/blocks/google_drive.test.ts @@ -1,10 +1,17 @@ import { triggersMock } from '@sim/testing/mocks/triggers.mock' import { describe, expect, it, vi } from 'vitest' +import type { BlockToolParamsContext } from '@/blocks/types' vi.mock('@/triggers', () => triggersMock) import { GoogleDriveBlock } from '@/blocks/blocks/google_drive' +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => GoogleDriveBlock.tools.config?.params?.(inputs, context) ?? {} + const paginationCases = [ { operation: 'list', subBlockId: 'pageToken' }, { operation: 'search', subBlockId: 'searchPageToken' }, @@ -14,8 +21,6 @@ const paginationCases = [ ] as const describe('GoogleDriveBlock pagination', () => { - const buildParams = GoogleDriveBlock.tools.config.params! - describe.each(paginationCases)('$operation', ({ operation, subBlockId }) => { /** * `pageToken` is the canonical tool param, so the `list` case would forward @@ -23,7 +28,7 @@ describe('GoogleDriveBlock pagination', () => { * ones the mapper has to translate, and none of them may survive as-is. */ it('forwards the page token to the tool under its own id', () => { - const params = buildParams({ operation, [subBlockId]: 'token-abc' }, undefined as never) + const params = buildParams({ operation, [subBlockId]: 'token-abc' }) expect(params).toMatchObject({ pageToken: 'token-abc' }) if (subBlockId !== 'pageToken') expect(params[subBlockId]).toBeUndefined() @@ -31,9 +36,7 @@ describe('GoogleDriveBlock pagination', () => { }) it('does not leak a page token into operations that do not paginate', () => { - expect( - buildParams({ operation: 'get_file', pageToken: 'token-abc' }, undefined as never).pageToken - ).toBeUndefined() + expect(buildParams({ operation: 'get_file', pageToken: 'token-abc' }).pageToken).toBeUndefined() }) /** @@ -46,7 +49,7 @@ describe('GoogleDriveBlock pagination', () => { '$subBlockId left over from a previous operation', ({ subBlockId }) => { it.each(['upload', 'get_file', 'list'])('is dropped under %s', (operation) => { - const params = buildParams({ operation, [subBlockId]: 'stale' }, undefined as never) + const params = buildParams({ operation, [subBlockId]: 'stale' }) expect(params.pageToken).toBeUndefined() expect(params[subBlockId]).toBeUndefined() @@ -55,18 +58,49 @@ describe('GoogleDriveBlock pagination', () => { ) it('prefers the operation-owned token when a stale sibling is also present', () => { - const params = buildParams( - { - operation: 'search', - searchPageToken: 'search-token', - commentsPageToken: 'stale', - pageToken: 'stale-canonical', - }, - undefined as never - ) + const params = buildParams({ + operation: 'search', + searchPageToken: 'search-token', + commentsPageToken: 'stale', + pageToken: 'stale-canonical', + }) expect(params.pageToken).toBe('search-token') expect(params.commentsPageToken).toBeUndefined() expect(params.searchPageToken).toBeUndefined() }) }) + +/** + * On an agent call the model supplies `pageSize`/`pageToken`/`query` under the tool's own names; + * the per-operation fields (`permissionsPageSize`, `searchPageToken`, `searchQuery`) are never rendered. + */ +describe('GoogleDriveBlock agent tool calls', () => { + const agentCall = (operation: string, args: Record): Record => { + const inputs = { ...args, operation } + return { ...inputs, ...buildParams(inputs, { operation }) } + } + + it.each(['search', 'list_permissions', 'list_revisions', 'list_comments'])( + 'keeps the model pageSize and pageToken on %s', + (operation) => { + const mapped = agentCall(operation, { fileId: 'f-1', pageSize: 5, pageToken: 'next' }) + expect(mapped.pageSize).toBe(5) + expect(mapped.pageToken).toBe('next') + } + ) + + it('keeps the model query on search', () => { + expect(agentCall('search', { query: 'budget' }).query).toBe('budget') + }) + + it('drops a list page token left over on the canvas under search', () => { + expect(buildParams({ operation: 'search', pageToken: 'stale' }).pageToken).toBeUndefined() + }) + + /** A stored agent tool keeps its operation on the tool, not in the args the mapper receives. */ + it('keeps the model pageToken when the args carry no operation', () => { + const args = { fileId: 'f-1', pageToken: 'next' } + expect({ ...args, ...buildParams(args, { operation: 'list_comments' }) }.pageToken).toBe('next') + }) +}) diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts index 183c02dcb2d..18e709cff6d 100644 --- a/apps/sim/blocks/blocks/google_drive.ts +++ b/apps/sim/blocks/blocks/google_drive.ts @@ -3,7 +3,12 @@ import { GoogleDriveIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { normalizeFileInput, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils' +import { + isToolCall, + normalizeFileInput, + SERVICE_ACCOUNT_SUBBLOCKS, + toolCallArg, +} from '@/blocks/utils' import { getTrigger } from '@/triggers' /** @@ -1530,7 +1535,7 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.` throw new Error(`Invalid Google Drive operation: ${params.operation}`) } }, - params: (params) => { + params: (params, context) => { const { oauthCredential, uploadFolderId, @@ -1685,19 +1690,33 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.` includeDeleted === 'true' ? true : includeDeleted === 'false' ? false : undefined let effectivePageSize: string | undefined = pageSize - if (params.operation === 'search') effectivePageSize = searchPageSize - else if (params.operation === 'list_revisions') effectivePageSize = revisionsPageSize - else if (params.operation === 'list_comments') effectivePageSize = commentsPageSize - else if (params.operation === 'list_permissions') effectivePageSize = permissionsPageSize + // `pageSize`, `pageToken` and `query` are List's fields; other operations trust them only from a tool call + const agentPageSize = toolCallArg(context, pageSize) + if (params.operation === 'search') effectivePageSize = searchPageSize || agentPageSize + else if (params.operation === 'list_revisions') + effectivePageSize = revisionsPageSize || agentPageSize + else if (params.operation === 'list_comments') + effectivePageSize = commentsPageSize || agentPageSize + else if (params.operation === 'list_permissions') + effectivePageSize = permissionsPageSize || agentPageSize + const agentPageToken = toolCallArg(context, pageToken) let effectivePageToken: string | undefined = pageToken - if (params.operation === 'search') effectivePageToken = searchPageToken - else if (params.operation === 'list_permissions') effectivePageToken = permissionsPageToken - else if (params.operation === 'list_revisions') effectivePageToken = revisionsPageToken - else if (params.operation === 'list_comments') effectivePageToken = commentsPageToken - else if (params.operation !== 'list') effectivePageToken = undefined + if (params.operation === 'search') effectivePageToken = searchPageToken || agentPageToken + else if (params.operation === 'list_permissions') + effectivePageToken = permissionsPageToken || agentPageToken + else if (params.operation === 'list_revisions') + effectivePageToken = revisionsPageToken || agentPageToken + else if (params.operation === 'list_comments') + effectivePageToken = commentsPageToken || agentPageToken + else if (params.operation !== 'list') effectivePageToken = agentPageToken - const effectiveQuery = params.operation === 'search' ? searchQuery : query + const effectiveQuery = + params.operation === 'search' + ? isToolCall(context) && !searchQuery + ? query + : searchQuery + : query const effectiveOrderBy = params.operation === 'list' diff --git a/apps/sim/blocks/blocks/google_groups.test.ts b/apps/sim/blocks/blocks/google_groups.test.ts index 3a218434b93..8889b65fa97 100644 --- a/apps/sim/blocks/blocks/google_groups.test.ts +++ b/apps/sim/blocks/blocks/google_groups.test.ts @@ -44,3 +44,11 @@ describe('GoogleGroupsBlock update settings', () => { expect(mapped.whoCanJoin).toBe('INVITED_CAN_JOIN') }) }) + +describe('GoogleGroupsBlock agent tool calls', () => { + it('keeps the model orderBy on list_groups when no sort order is chosen', () => { + const inputs = { operation: 'list_groups', customer: 'my_customer', orderBy: 'email' } + const mapped = GoogleGroupsBlock.tools.config?.params?.(inputs, { operation: 'list_groups' }) + expect({ ...inputs, ...mapped }.orderBy).toBe('email') + }) +}) diff --git a/apps/sim/blocks/blocks/google_groups.ts b/apps/sim/blocks/blocks/google_groups.ts index 4b94aa69d7c..5948e35f745 100644 --- a/apps/sim/blocks/blocks/google_groups.ts +++ b/apps/sim/blocks/blocks/google_groups.ts @@ -2,7 +2,7 @@ import { GoogleGroupsIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils' +import { SERVICE_ACCOUNT_SUBBLOCKS, toolCallArg } from '@/blocks/utils' /** Settings the Update Settings operation forwards when set; empty means unchanged. */ const UPDATE_SETTINGS_FIELDS = [ @@ -726,7 +726,7 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, throw new Error(`Invalid Google Groups operation: ${params.operation}`) } }, - params: (params) => { + params: (params, context) => { const { oauthCredential, operation, ...rest } = params switch (operation) { @@ -739,7 +739,7 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, maxResults: rest.maxResults ? Number(rest.maxResults) : undefined, pageToken: rest.pageToken || undefined, userKey: rest.userKey || undefined, - orderBy: rest.sortOrder ? 'email' : undefined, + orderBy: toolCallArg(context, rest.orderBy) || (rest.sortOrder ? 'email' : undefined), sortOrder: rest.sortOrder || undefined, } case 'get_group': diff --git a/apps/sim/blocks/blocks/google_vault.test.ts b/apps/sim/blocks/blocks/google_vault.test.ts new file mode 100644 index 00000000000..9cb2b713243 --- /dev/null +++ b/apps/sim/blocks/blocks/google_vault.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' +import { GoogleVaultBlock } from '@/blocks/blocks/google_vault' +import type { BlockToolParamsContext } from '@/blocks/types' + +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => GoogleVaultBlock.tools.config?.params?.(inputs, context) ?? {} + +/** + * On an agent call the model supplies `includeSharedDrives` under the tool's own name; the + * dedicated `exportIncludeSharedDrives` switch is never rendered in the agent tool input. + */ +describe('GoogleVaultBlock includeSharedDrives', () => { + it.each(['create_matters_export', 'create_saved_query'])( + 'keeps the model value on an agent %s call', + (operation) => { + const inputs = { operation, matterId: 'm-1', corpus: 'DRIVE', includeSharedDrives: true } + expect(buildParams(inputs, { operation }).includeSharedDrives).toBe(true) + } + ) + + it('still sends false on the canvas for an unset switch', () => { + const inputs = { + operation: 'create_matters_export', + corpus: 'DRIVE', + includeSharedDrives: true, + } + expect(buildParams(inputs).includeSharedDrives).toBe(false) + }) +}) diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index ea4c05148fe..a8823cb7f7c 100644 --- a/apps/sim/blocks/blocks/google_vault.ts +++ b/apps/sim/blocks/blocks/google_vault.ts @@ -3,7 +3,7 @@ import { GoogleVaultIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils' +import { parseOptionalBooleanInput, SERVICE_ACCOUNT_SUBBLOCKS, toolCallArg } from '@/blocks/utils' export const GoogleVaultBlock: BlockConfig = { type: 'google_vault', @@ -906,7 +906,7 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, throw new Error(`Invalid Google Vault operation: ${params.operation}`) } }, - params: (params) => { + params: (params, context) => { const { oauthCredential, holdStartTime, @@ -958,7 +958,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, ...(params.operation === 'list_matters' && matterState && { state: matterState }), ...(isQueryOperation && { includeSharedDrives: - exportIncludeSharedDrives === true || exportIncludeSharedDrives === 'true', + exportIncludeSharedDrives == null + ? (parseOptionalBooleanInput(toolCallArg(context, rest.includeSharedDrives)) ?? + false) + : exportIncludeSharedDrives === true || exportIncludeSharedDrives === 'true', ...(dataScope && { dataScope }), }), ...(params.operation === 'create_matters_export' && exportFormat && { exportFormat }), diff --git a/apps/sim/blocks/blocks/hex.test.ts b/apps/sim/blocks/blocks/hex.test.ts new file mode 100644 index 00000000000..5e632ff4f64 --- /dev/null +++ b/apps/sim/blocks/blocks/hex.test.ts @@ -0,0 +1,101 @@ +/** + * The executor hands a tool `{ ...rawInputs, ...blockParams }`, and an advanced subBlock with a + * stored value serializes whatever operation is selected. These cases keep values stored for one + * Hex operation out of another operation's request. + */ +import { describe, expect, it } from 'vitest' +import { HexBlock } from '@/blocks/blocks/hex' +import type { BlockToolParamsContext } from '@/blocks/types' +import { getProjectRunsTool } from '@/tools/hex/get_project_runs' +import { getQueriedTablesTool } from '@/tools/hex/get_queried_tables' +import { listGroupsTool } from '@/tools/hex/list_groups' +import { listUsersTool } from '@/tools/hex/list_users' +import type { ToolConfig } from '@/tools/types' + +const PROJECT_ID = '11111111-1111-4111-8111-111111111111' + +function requestUrl( + tool: ToolConfig, + inputs: Record, + context?: BlockToolParamsContext +): URL { + const mapped = HexBlock.tools.config?.params?.(inputs, context) ?? {} + const params = { apiKey: 'hex-token', ...inputs, ...mapped } + const url = tool.request.url + return new URL(typeof url === 'function' ? url(params) : url) +} + +describe('Hex block keeps stale subBlock values out of tool requests', () => { + it('get project runs ignores the List Projects status filter', () => { + const url = requestUrl(getProjectRunsTool, { + operation: 'get_project_runs', + projectId: PROJECT_ID, + statusFilter: 'PUBLISHED', + }) + expect(url.searchParams.has('statusFilter')).toBe(false) + }) + + it('get project runs still sends its own status filter', () => { + const url = requestUrl(getProjectRunsTool, { + operation: 'get_project_runs', + projectId: PROJECT_ID, + statusFilter: 'PUBLISHED', + runStatusFilter: 'ERRORED', + }) + expect(url.searchParams.get('statusFilter')).toBe('ERRORED') + }) + + it('list users drops a List Projects sort field it does not accept', () => { + const url = requestUrl(listUsersTool, { operation: 'list_users', sortBy: 'LAST_EDITED_AT' }) + expect(url.searchParams.has('sortBy')).toBe(false) + }) + + it('list groups keeps a stored sort field it accepts', () => { + const url = requestUrl(listGroupsTool, { operation: 'list_groups', sortBy: 'CREATED_AT' }) + expect(url.searchParams.get('sortBy')).toBe('CREATED_AT') + }) + + it('get queried tables ignores the list operations cursors', () => { + const url = requestUrl(getQueriedTablesTool, { + operation: 'get_queried_tables', + projectId: PROJECT_ID, + after: 'projects-cursor', + before: 'projects-cursor', + }) + expect(url.searchParams.has('after')).toBe(false) + expect(url.searchParams.has('before')).toBe(false) + }) + + it('get queried tables sends its own cursor', () => { + const url = requestUrl(getQueriedTablesTool, { + operation: 'get_queried_tables', + projectId: PROJECT_ID, + after: 'projects-cursor', + queriedTablesAfter: 'tables-cursor', + }) + expect(url.searchParams.get('after')).toBe('tables-cursor') + }) + + it('get project runs keeps a run status an agent passes directly', () => { + const args = { operation: 'get_project_runs', projectId: PROJECT_ID, statusFilter: 'errored' } + const built = requestUrl(getProjectRunsTool, args, { operation: 'get_project_runs' }) + expect(built.searchParams.get('statusFilter')).toBe('errored') + }) + + it('list groups keeps a user id an agent passes directly', () => { + const args = { operation: 'list_groups', userId: PROJECT_ID } + const built = requestUrl(listGroupsTool, args, { operation: 'list_groups' }) + expect(built.searchParams.get('userId')).toBe(PROJECT_ID) + }) + + it('list groups ignores a Deactivate User id left on the canvas', () => { + const url = requestUrl(listGroupsTool, { operation: 'list_groups', userId: PROJECT_ID }) + expect(url.searchParams.has('userId')).toBe(false) + }) + + it('get queried tables keeps a cursor an agent passes directly', () => { + const args = { operation: 'get_queried_tables', projectId: PROJECT_ID, after: 'tables-cursor' } + const built = requestUrl(getQueriedTablesTool, args, { operation: 'get_queried_tables' }) + expect(built.searchParams.get('after')).toBe('tables-cursor') + }) +}) diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index ee2e8f01528..9d23f46547d 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -1,7 +1,33 @@ import { HexIcon } from '@/components/icons' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { withSavedCredential } from '@/blocks/utils' +import { isToolCall, toolCallArg, withSavedCredential } from '@/blocks/utils' + +/** + * Sort fields each list endpoint accepts. The only Sort By field is the List Projects one, so a + * value stored there reaches the other list tools raw; one they reject is dropped instead of sent. + */ +const HEX_LIST_SORT_FIELDS: Record = { + list_users: ['NAME', 'EMAIL'], + list_groups: ['CREATED_AT', 'NAME'], + list_collections: ['NAME'], + list_data_connections: ['NAME', 'CREATED_AT'], +} + +/** Run statuses Get Project Runs accepts; the List Projects status filter shares its param name. */ +const HEX_RUN_STATUSES: readonly string[] = [ + 'PENDING', + 'RUNNING', + 'ERRORED', + 'COMPLETED', + 'KILLED', + 'UNABLE_TO_ALLOCATE_KERNEL', +] + +function emptyToUndefined(value: unknown): unknown { + if (typeof value === 'string') return value.trim() || undefined + return value ?? undefined +} export const HexBlock: BlockConfig = { type: 'hex', @@ -59,7 +85,11 @@ export const HexBlock: BlockConfig = { { text: 'in group', field: 'groupId' }, { text: ', up to', field: 'limit' }, ], - list_groups: ['List groups', { text: ', up to', field: 'limit' }], + list_groups: [ + 'List groups', + { text: 'of user', field: 'groupsUserId' }, + { text: ', up to', field: 'limit' }, + ], get_group: [{ text: 'Fetch group', field: 'groupIdInput', core: true }], list_collections: ['List collections', { text: ', up to', field: 'limit' }], get_collection: [{ text: 'Fetch collection', field: 'collectionId', core: true }], @@ -546,6 +576,30 @@ Do not include any explanations, markdown formatting, or other text outside the condition: { field: 'operation', value: 'list_users' }, mode: 'advanced', }, + { + id: 'groupsUserId', + title: 'Filter by User ID', + type: 'short-input', + placeholder: 'User UUID whose groups to list (optional)', + condition: { field: 'operation', value: 'list_groups' }, + mode: 'advanced', + }, + { + id: 'queriedTablesAfter', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor for the next page', + condition: { field: 'operation', value: 'get_queried_tables' }, + mode: 'advanced', + }, + { + id: 'queriedTablesBefore', + title: 'Before Cursor', + type: 'short-input', + placeholder: 'Cursor for the previous page', + condition: { field: 'operation', value: 'get_queried_tables' }, + mode: 'advanced', + }, { id: 'after', title: 'After Cursor', @@ -655,15 +709,33 @@ Do not include any explanations, markdown formatting, or other text outside the return 'hex_run_project' } }, - params: (params) => { + params: (params, context) => { const result: Record = {} const op = params.operation + const fieldOrToolArg = (field: unknown, arg: unknown) => + emptyToUndefined(field) ?? emptyToUndefined(toolCallArg(context, arg)) if (params.limit) result.limit = Number(params.limit) if (op === 'get_project_runs' && params.offset) result.offset = Number(params.offset) if (op === 'update_project' && params.projectStatus) result.status = params.projectStatus - if (op === 'get_project_runs' && params.runStatusFilter) - result.statusFilter = params.runStatusFilter + if (op === 'get_project_runs') { + // An agent passes `statusFilter` directly; on the canvas it is the List Projects status. + result.statusFilter = + emptyToUndefined(params.runStatusFilter) ?? + (isToolCall(context) || HEX_RUN_STATUSES.includes(params.statusFilter) + ? emptyToUndefined(params.statusFilter) + : undefined) + } + const sortFields = HEX_LIST_SORT_FIELDS[op] + if (sortFields) { + result.sortBy = sortFields.includes(params.sortBy) ? params.sortBy : undefined + } + if (op === 'get_queried_tables') { + // On the canvas the shared list cursors can hold another operation's stale value. + result.after = fieldOrToolArg(params.queriedTablesAfter, params.after) + result.before = fieldOrToolArg(params.queriedTablesBefore, params.before) + } + if (op === 'list_groups') result.userId = fieldOrToolArg(params.groupsUserId, params.userId) if ( (op === 'get_group' || op === 'update_group' || op === 'delete_group') && params.groupIdInput @@ -735,6 +807,9 @@ Do not include any explanations, markdown formatting, or other text outside the runTriggerFilter: { type: 'string', description: 'Filter runs by trigger source' }, groupId: { type: 'string', description: 'Filter users by group UUID' }, userIds: { type: 'string', description: 'Comma-separated user UUIDs to filter by' }, + groupsUserId: { type: 'string', description: 'List only the groups this user UUID belongs to' }, + queriedTablesAfter: { type: 'string', description: 'Queried tables cursor to fetch after' }, + queriedTablesBefore: { type: 'string', description: 'Queried tables cursor to fetch before' }, groupIdInput: { type: 'string', description: 'Group UUID for get/update/delete group' }, groupName: { type: 'string', description: 'Group name' }, groupMemberUserIds: { type: 'json', description: 'Initial member user UUIDs for new group' }, @@ -753,7 +828,11 @@ Do not include any explanations, markdown formatting, or other text outside the runId: { type: 'string', description: 'Run UUID' }, runUrl: { type: 'string', description: 'URL to view the run' }, runStatusUrl: { type: 'string', description: 'URL to check run status' }, - projectVersion: { type: 'number', description: 'Project version number' }, + projectVersion: { + type: 'any', + description: + 'Project version: a number from Run Project; Get Run Status returns it as a string ("draft" for notebook runs)', + }, // Run status outputs status: { type: 'json', @@ -761,7 +840,11 @@ Do not include any explanations, markdown formatting, or other text outside the }, startTime: { type: 'string', description: 'Run start time' }, endTime: { type: 'string', description: 'Run end time' }, - elapsedTime: { type: 'number', description: 'Elapsed time in seconds' }, + runTrigger: { + type: 'string', + description: 'How the run was triggered (API, SCHEDULED, APP_REFRESH)', + }, + elapsedTime: { type: 'number', description: 'Elapsed run time in milliseconds' }, traceId: { type: 'string', description: 'Trace ID for debugging' }, // Project outputs id: { type: 'string', description: 'Resource ID' }, @@ -783,7 +866,7 @@ Do not include any explanations, markdown formatting, or other text outside the runs: { type: 'json', description: - 'List of runs with runId, status, runUrl, startTime, endTime, elapsedTime, projectVersion', + 'List of runs with runId, status, runTrigger, runUrl, startTime, endTime, elapsedTime, projectVersion', }, users: { type: 'json', diff --git a/apps/sim/blocks/blocks/microsoft_ad.test.ts b/apps/sim/blocks/blocks/microsoft_ad.test.ts index 88265e4736b..12f343f31a8 100644 --- a/apps/sim/blocks/blocks/microsoft_ad.test.ts +++ b/apps/sim/blocks/blocks/microsoft_ad.test.ts @@ -1,5 +1,12 @@ import { describe, expect, it } from 'vitest' import { MicrosoftAdBlock } from '@/blocks/blocks/microsoft_ad' +import type { BlockToolParamsContext } from '@/blocks/types' + +/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */ +const buildParams = ( + inputs: Record, + context?: BlockToolParamsContext +): Record => MicrosoftAdBlock.tools.config?.params?.(inputs, context) ?? {} /** * The tri-state assertions run against `{ ...inputs, ...buildParams(inputs) }`, the shape the @@ -8,8 +15,6 @@ import { MicrosoftAdBlock } from '@/blocks/blocks/microsoft_ad' * pass against the broken code. */ describe('MicrosoftAdBlock', () => { - const buildParams = MicrosoftAdBlock.tools.config.params! - const subBlock = (id: string) => MicrosoftAdBlock.subBlocks.find((candidate) => candidate.id === id)! @@ -73,3 +78,35 @@ describe('MicrosoftAdBlock', () => { }) }) }) + +/** + * On an agent call the model supplies `filter`/`search`/`transitive` under the tool's own names; + * the per-operation fields (`signInFilter`, `deviceSearch`, `membershipScope`) are never rendered. + */ +describe('MicrosoftAdBlock agent tool calls', () => { + const agentCall = (operation: string, args: Record): Record => { + const inputs = { ...args, operation } + return { ...inputs, ...buildParams(inputs, { operation }) } + } + + it('keeps the model filter on an operation with a dedicated filter field', () => { + const mapped = agentCall('list_sign_ins', { filter: 'status/errorCode eq 0' }) + expect(mapped.filter).toBe('status/errorCode eq 0') + }) + + it('keeps the model search on an operation with a dedicated search field', () => { + const mapped = agentCall('list_devices', { search: '"displayName:laptop"' }) + expect(mapped.search).toBe('"displayName:laptop"') + }) + + it('keeps the model transitive flag when no membership scope is chosen', () => { + expect(agentCall('list_user_memberships', { userId: 'u-1', transitive: true }).transitive).toBe( + true + ) + }) + + it('drops a shared filter left over on the canvas for an operation with its own field', () => { + const inputs = { operation: 'list_sign_ins', filter: 'stale' } + expect({ ...inputs, ...buildParams(inputs) }.filter).toBeUndefined() + }) +}) diff --git a/apps/sim/blocks/blocks/microsoft_ad.ts b/apps/sim/blocks/blocks/microsoft_ad.ts index 26efed35581..6e46d5e6bb6 100644 --- a/apps/sim/blocks/blocks/microsoft_ad.ts +++ b/apps/sim/blocks/blocks/microsoft_ad.ts @@ -2,6 +2,7 @@ import { AzureIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' +import { parseOptionalBooleanInput, toolCallArg } from '@/blocks/utils' /** Operations that act on a single user and therefore require the User ID field. */ const USER_ID_OPERATIONS = [ @@ -854,19 +855,27 @@ export const MicrosoftAdBlock: BlockConfig = { ], config: { tool: (params) => `microsoft_ad_${params.operation}`, - params: (params) => { + params: (params, context) => { const result: Record = {} const top = Number(params.top) result.top = params.top && Number.isFinite(top) ? top : undefined if (params.nextLink) result.nextLink = params.nextLink const values = params as Record - result.filter = values[FILTER_FIELD_BY_OPERATION[params.operation]] || undefined - result.search = values[SEARCH_FIELD_BY_OPERATION[params.operation]] || undefined + result.filter = + values[FILTER_FIELD_BY_OPERATION[params.operation]] || + toolCallArg(context, params.filter) || + undefined + result.search = + values[SEARCH_FIELD_BY_OPERATION[params.operation]] || + toolCallArg(context, params.search) || + undefined if (params.operation === 'get_user') { result.includeManager = params.includeManager === true || params.includeManager === 'true' } if (params.operation === 'list_user_memberships') { - result.transitive = params.membershipScope === 'transitive' + result.transitive = params.membershipScope + ? params.membershipScope === 'transitive' + : (parseOptionalBooleanInput(toolCallArg(context, params.transitive)) ?? false) } if (params.operation === 'set_password') { result.forceChangePasswordNextSignIn = params.forceChangePasswordNextSignIn !== 'false' diff --git a/apps/sim/blocks/blocks/monaco.test.ts b/apps/sim/blocks/blocks/monaco.test.ts new file mode 100644 index 00000000000..c56a7f41ef6 --- /dev/null +++ b/apps/sim/blocks/blocks/monaco.test.ts @@ -0,0 +1,71 @@ +/** + * Workflow runs hand a tool `{ ...rawInputs, ...mappedParams }`, so the mapper must clear + * values another operation left in a shared subBlock. Agent tool calls pass the operation in + * context and supply arguments under tool param names, which the mapper must keep. + */ +import { describe, expect, it } from 'vitest' +import { MonacoBlock } from '@/blocks/blocks/monaco' +import { monacoCreateTagTool } from '@/tools/monaco/create_tag' +import { monacoCreateTaskTool } from '@/tools/monaco/create_task' +import { monacoUpdateSequenceTool } from '@/tools/monaco/update_sequence' +import type { ToolConfig } from '@/tools/types' + +const CREDENTIAL = { oauthCredential: 'cred-1', accessToken: 'mks_test' } + +function requestBody(tool: ToolConfig, params: Record) { + const body = tool.request.body + if (typeof body !== 'function') throw new Error(`${tool.id} has no request body`) + return body(params) +} + +function workflowParams(inputs: Record): Record { + const mapped = MonacoBlock.tools.config?.params?.(inputs) ?? {} + return { ...inputs, ...mapped, accessToken: CREDENTIAL.accessToken } +} + +function agentParams(operation: string, args: Record): Record { + const inputs = { ...CREDENTIAL, ...args } + const mapped = MonacoBlock.tools.config?.params?.(inputs, { operation }) ?? {} + return { ...inputs, ...mapped, accessToken: CREDENTIAL.accessToken } +} + +describe('Monaco block parameter mapping', () => { + it('sends the task title, not a job title left in the contact Title field', () => { + const params = workflowParams({ + ...CREDENTIAL, + operation: 'monaco_create_task', + title: 'VP Sales', + taskTitle: 'Call Ada', + }) + expect(requestBody(monacoCreateTaskTool, params)).toEqual({ title: 'Call Ada' }) + }) + + it('drops a contact job title when the task title is empty', () => { + const params = workflowParams({ + ...CREDENTIAL, + operation: 'monaco_create_task', + title: 'VP Sales', + taskTitle: '', + }) + expect(params.title).toBeUndefined() + }) + + it('keeps agent arguments named after tool params', () => { + const tag = agentParams('monaco_create_tag', { + object: 'accounts', + name: 'Hot Lead', + color: 'red', + }) + expect(requestBody(monacoCreateTagTool, tag)).toEqual({ + object: 'accounts', + name: 'Hot Lead', + color: 'red', + }) + + const sequence = agentParams('monaco_update_sequence', { + sequenceId: '0b2c7c1e-4d0a-4c8e-9a51-2f4f3e6a7b10', + action: 'pause', + }) + expect(requestBody(monacoUpdateSequenceTool, sequence)).toEqual({ action: 'pause' }) + }) +}) diff --git a/apps/sim/blocks/blocks/monaco.ts b/apps/sim/blocks/blocks/monaco.ts new file mode 100644 index 00000000000..aa3af55c992 --- /dev/null +++ b/apps/sim/blocks/blocks/monaco.ts @@ -0,0 +1,1903 @@ +import { toBooleanFromInput } from '@sim/utils/coerce' +import { MonacoIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +type FieldKind = 'string' | 'number' | 'boolean' | 'value' + +/** One tool param filled from one subBlock, coerced by `kind` at execution time. */ +type FieldBinding = readonly [param: string, field: string, kind: FieldKind] + +const LIST_FIELDS: readonly FieldBinding[] = [ + ['filters', 'filters', 'value'], + ['sort', 'sort', 'string'], + ['page', 'page', 'number'], + ['pageSize', 'pageSize', 'number'], +] + +const PAGE_FIELDS: readonly FieldBinding[] = [ + ['page', 'page', 'number'], + ['pageSize', 'pageSize', 'number'], +] + +const CONTACT_WRITE_FIELDS: readonly FieldBinding[] = [ + ['email', 'email', 'string'], + ['linkedinUrl', 'linkedinUrl', 'string'], + ['firstName', 'firstName', 'string'], + ['lastName', 'lastName', 'string'], + ['title', 'title', 'string'], + ['phoneNumber', 'phoneNumber', 'string'], + ['location', 'location', 'string'], + ['accountId', 'accountId', 'string'], + ['domain', 'domain', 'string'], + ['doNotContact', 'doNotContact', 'boolean'], + ['notes', 'notes', 'string'], + ['tags', 'tags', 'value'], + ['isInbound', 'isInbound', 'boolean'], + ['customFields', 'customFields', 'value'], +] + +const ACCOUNT_WRITE_FIELDS: readonly FieldBinding[] = [ + ['name', 'name', 'string'], + ['status', 'accountStatus', 'string'], + ['notes', 'notes', 'string'], + ['owner', 'owner', 'string'], + ['parentAccount', 'parentAccount', 'string'], + ['companyId', 'companyId', 'string'], + ['replaceEditedFields', 'replaceEditedFields', 'boolean'], + ['linkedinUrl', 'linkedinUrl', 'string'], + ['description', 'companyDescription', 'string'], + ['employeeCount', 'employeeCount', 'number'], + ['foundedYear', 'foundedYear', 'number'], + ['estimatedAnnualRevenueUsd', 'estimatedAnnualRevenueUsd', 'number'], + ['totalFundingUsd', 'totalFundingUsd', 'number'], + ['lastFundingRound', 'lastFundingRound', 'string'], + ['lastFundingDate', 'lastFundingDate', 'string'], + ['industries', 'industries', 'value'], + ['businessModel', 'businessModel', 'string'], + ['companyStatus', 'companyStatus', 'string'], + ['location', 'location', 'string'], + ['tags', 'tags', 'value'], + ['domains', 'domains', 'value'], + ['resetFields', 'resetFields', 'value'], + ['clearFields', 'clearFields', 'value'], + ['customFields', 'customFields', 'value'], +] + +const OPPORTUNITY_WRITE_FIELDS: readonly FieldBinding[] = [ + ['name', 'name', 'string'], + ['pipelineId', 'pipelineId', 'string'], + ['stage', 'stage', 'string'], + ['stageId', 'stageId', 'string'], + ['ownerUserId', 'ownerUserId', 'string'], + ['amount', 'amount', 'number'], + ['currencyCode', 'currencyCode', 'string'], + ['estimatedCloseDate', 'estimatedCloseDate', 'string'], + ['notes', 'notes', 'string'], + ['tags', 'tags', 'value'], + ['customFields', 'customFields', 'value'], +] + +/** + * The subBlock each tool param of every operation reads. The executor merges raw subBlock + * values under the mapped params, so the mapper clears every field and sets only these. + */ +const OPERATION_FIELDS = { + monaco_list_contacts: [...LIST_FIELDS, ['includeCustomFields', 'includeCustomFields', 'boolean']], + monaco_get_contact: [ + ['contactId', 'contactId', 'string'], + ['includeReasoning', 'includeReasoning', 'boolean'], + ], + monaco_create_contact: CONTACT_WRITE_FIELDS, + monaco_update_contact: [ + ['contactId', 'contactId', 'string'], + ['firstName', 'firstName', 'string'], + ['lastName', 'lastName', 'string'], + ['email', 'email', 'string'], + ['title', 'title', 'string'], + ['phoneNumber', 'phoneNumber', 'string'], + ['linkedinUrl', 'linkedinUrl', 'string'], + ['location', 'location', 'string'], + ['notes', 'notes', 'string'], + ['doNotContact', 'doNotContact', 'boolean'], + ['tags', 'tags', 'value'], + ['country', 'country', 'string'], + ['jobStartDate', 'jobStartDate', 'string'], + ['clearFields', 'clearFields', 'value'], + ['customFields', 'customFields', 'value'], + ], + monaco_upsert_contact: [ + ...CONTACT_WRITE_FIELDS, + ['id', 'contactId', 'string'], + ['clearFields', 'clearFields', 'value'], + ], + monaco_delete_contact: [['contactId', 'contactId', 'string']], + monaco_list_accounts: [...LIST_FIELDS, ['includeCustomFields', 'includeCustomFields', 'boolean']], + monaco_get_account: [ + ['accountId', 'accountId', 'string'], + ['includeReasoning', 'includeReasoning', 'boolean'], + ], + monaco_upsert_account: [ + ['domainLookup', 'domainLookup', 'string'], + ['id', 'accountId', 'string'], + ...ACCOUNT_WRITE_FIELDS, + ], + monaco_update_account: [['accountId', 'accountId', 'string'], ...ACCOUNT_WRITE_FIELDS], + monaco_delete_account: [['accountId', 'accountId', 'string']], + monaco_list_opportunities: [ + ...LIST_FIELDS, + ['pipelineId', 'pipelineId', 'string'], + ['includeCustomFields', 'includeCustomFields', 'boolean'], + ], + monaco_get_opportunity: [ + ['opportunityId', 'opportunityId', 'string'], + ['includeReasoning', 'includeReasoning', 'boolean'], + ], + monaco_create_opportunity: [ + ...OPPORTUNITY_WRITE_FIELDS, + ['accountId', 'accountId', 'string'], + ['idempotencyKey', 'idempotencyKey', 'string'], + ], + monaco_update_opportunity: [ + ['opportunityId', 'opportunityId', 'string'], + ...OPPORTUNITY_WRITE_FIELDS, + ['actualCloseDate', 'actualCloseDate', 'string'], + ], + monaco_delete_opportunity: [['opportunityId', 'opportunityId', 'string']], + monaco_list_opportunity_contacts: [['opportunityId', 'opportunityId', 'string'], ...PAGE_FIELDS], + monaco_link_opportunity_contacts: [ + ['opportunityId', 'opportunityId', 'string'], + ['contactIds', 'contactIds', 'value'], + ], + monaco_unlink_opportunity_contacts: [ + ['opportunityId', 'opportunityId', 'string'], + ['contactIds', 'contactIds', 'value'], + ], + monaco_list_pipelines: [], + monaco_list_tasks: LIST_FIELDS, + monaco_get_task: [['taskId', 'taskId', 'string']], + monaco_create_task: [ + ['title', 'taskTitle', 'string'], + ['description', 'taskDescription', 'string'], + ['accountId', 'accountId', 'string'], + ['opportunityId', 'opportunityId', 'string'], + ['taskType', 'taskType', 'string'], + ['assigneeId', 'assigneeId', 'string'], + ['dueAt', 'dueAt', 'string'], + ['messageDraft', 'messageDraft', 'value'], + ], + monaco_update_task: [ + ['taskId', 'taskId', 'string'], + ['title', 'taskTitle', 'string'], + ['description', 'taskDescription', 'string'], + ['status', 'taskStatus', 'string'], + ['accountId', 'accountId', 'string'], + ['assigneeId', 'assigneeId', 'string'], + ['dueAt', 'dueAt', 'string'], + ['messageDraft', 'messageDraft', 'value'], + ], + monaco_list_meetings: LIST_FIELDS, + monaco_get_meeting: [['meetingId', 'meetingId', 'string']], + monaco_list_messages: [ + ['contactId', 'contactId', 'string'], + ['accountId', 'accountId', 'string'], + ['channels', 'channels', 'value'], + ['cursor', 'cursor', 'string'], + ['pageSize', 'pageSize', 'number'], + ], + monaco_get_conversation: [ + ['conversationId', 'conversationId', 'string'], + ['channel', 'channel', 'string'], + ...PAGE_FIELDS, + ], + monaco_list_tags: [['object', 'tagObject', 'string']], + monaco_create_tag: [ + ['object', 'tagObject', 'string'], + ['name', 'tagName', 'string'], + ['color', 'tagColor', 'string'], + ], + monaco_list_users: [['query', 'userQuery', 'string']], + monaco_get_current_user: [], + monaco_get_field_schemas: [['entity', 'entity', 'string']], + monaco_list_sequences: LIST_FIELDS, + monaco_get_sequence: [['sequenceId', 'sequenceId', 'string']], + monaco_update_sequence: [ + ['sequenceId', 'sequenceId', 'string'], + ['action', 'sequenceAction', 'string'], + ], + monaco_list_audiences: PAGE_FIELDS, + monaco_get_audience: [['audienceId', 'audienceId', 'string']], + monaco_list_audience_contacts: [['audienceId', 'audienceId', 'string'], ...PAGE_FIELDS], + monaco_create_audience: [ + ['name', 'audienceName', 'string'], + ['contactIds', 'contactIds', 'value'], + ['filters', 'audienceFilters', 'value'], + ['excludedIds', 'excludedIds', 'value'], + ['sourceAudienceId', 'sourceAudienceId', 'string'], + ], + monaco_add_audience_contacts: [ + ['audienceId', 'audienceId', 'string'], + ['contactIds', 'contactIds', 'value'], + ], + monaco_remove_audience_contacts: [ + ['audienceId', 'audienceId', 'string'], + ['contactIds', 'contactIds', 'value'], + ], + monaco_list_campaigns: LIST_FIELDS, + monaco_get_campaign: [['campaignId', 'campaignId', 'string']], + monaco_enroll_campaign_contacts: [ + ['campaignId', 'campaignId', 'string'], + ['audienceName', 'audienceName', 'string'], + ['contactIds', 'contactIds', 'value'], + ], + monaco_add_campaign_audiences: [ + ['campaignId', 'campaignId', 'string'], + ['audienceIds', 'audienceIds', 'value'], + ], +} as const satisfies Record + +type MonacoOperation = keyof typeof OPERATION_FIELDS + +/** Every subBlock id and tool param name the mapper may set, cleared before each run. */ +const ALL_FIELD_KEYS = [ + ...new Set( + Object.values(OPERATION_FIELDS).flatMap((bindings: readonly FieldBinding[]) => + bindings.flatMap((binding) => [binding[0], binding[1]]) + ) + ), +] + +function isMonacoOperation(value: unknown): value is MonacoOperation { + return typeof value === 'string' && Object.hasOwn(OPERATION_FIELDS, value) +} + +function isBlank(value: unknown): boolean { + return value === undefined || value === null || (typeof value === 'string' && !value.trim()) +} + +function coerceField(value: unknown, kind: FieldKind, param: string): unknown { + if (isBlank(value)) return undefined + if (kind === 'boolean') return toBooleanFromInput(value) + if (kind === 'number') { + const parsed = typeof value === 'number' ? value : Number(String(value).trim()) + if (!Number.isFinite(parsed)) throw new Error(`${param} must be a number`) + return parsed + } + return value +} + +const ops = (...values: MonacoOperation[]) => ({ field: 'operation', value: values }) + +const LIST_OPERATIONS: MonacoOperation[] = [ + 'monaco_list_contacts', + 'monaco_list_accounts', + 'monaco_list_opportunities', + 'monaco_list_tasks', + 'monaco_list_meetings', + 'monaco_list_sequences', + 'monaco_list_campaigns', +] + +const PAGED_OPERATIONS: MonacoOperation[] = [ + ...LIST_OPERATIONS, + 'monaco_list_opportunity_contacts', + 'monaco_get_conversation', + 'monaco_list_audiences', + 'monaco_list_audience_contacts', +] + +const CONTACT_WRITE_OPERATIONS: MonacoOperation[] = [ + 'monaco_create_contact', + 'monaco_update_contact', + 'monaco_upsert_contact', +] + +const ACCOUNT_WRITE_OPERATIONS: MonacoOperation[] = [ + 'monaco_upsert_account', + 'monaco_update_account', +] + +const OPPORTUNITY_WRITE_OPERATIONS: MonacoOperation[] = [ + 'monaco_create_opportunity', + 'monaco_update_opportunity', +] + +const TASK_WRITE_OPERATIONS: MonacoOperation[] = ['monaco_create_task', 'monaco_update_task'] + +const BOOLEAN_CHOICES = [ + { label: 'Default', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, +] + +const DATE_WAND = (subject: string) => ({ + enabled: true, + placeholder: `Describe the ${subject}, e.g. "end of next month"`, + prompt: `Generate the ${subject} as a calendar date in YYYY-MM-DD format based on the user's description. Return ONLY the date string.`, + generationType: 'timestamp' as const, +}) + +const ID_LIST_WAND = (subject: string) => ({ + enabled: true, + placeholder: `Paste text or data containing the ${subject}`, + prompt: `Extract the ${subject} from the input. Return ONLY a JSON array of ID strings, with duplicates removed.`, + generationType: 'json-array' as const, +}) + +const FILTERS_WAND = { + enabled: true, + placeholder: + 'Describe which records to match, e.g. "created this quarter with title containing VP"', + prompt: + 'Generate a Monaco filter as a JSON array of {"field", "condition", "value"} rules, which are ANDed, or as {"operator": "and" | "or", "filters": [...]} for OR or nested logic. Conditions include is, is_not, equals, contains, not_contains, is_empty, is_not_empty, is_before, and is_after. Use snake_case field keys such as status, created_at, or custom_field_, enum keys rather than display names, and ISO 8601 dates. Return ONLY the JSON.', + generationType: 'json-value' as const, +} + +export const MonacoBlock: BlockConfig = { + type: 'monaco', + name: 'Monaco', + description: 'Manage Monaco CRM records, tasks, meetings, and outreach', + longDescription: + 'Connect a Monaco API key to read and write contacts, accounts, and opportunities, manage tasks and tags, read meetings, transcripts, and email or LinkedIn conversations, and run outreach by building audiences, enrolling contacts in campaigns, and controlling sequences.', + docsLink: 'https://docs.sim.ai/integrations/monaco', + category: 'tools', + integrationType: IntegrationType.Sales, + bgColor: '#000000', + icon: MonacoIcon, + authMode: AuthMode.ApiKey, + canvasPresentation: { + defaultTitle: 'Monaco', + sentences: { + byOperation: { + monaco_list_contacts: ['List contacts', { text: 'matching', field: 'filters' }], + monaco_get_contact: [{ text: 'Get contact', field: 'contactId', core: true }], + monaco_create_contact: [ + 'Create contact', + { text: 'for', field: 'email' }, + { text: 'or', field: 'linkedinUrl' }, + ], + monaco_update_contact: [{ text: 'Update contact', field: 'contactId', core: true }], + monaco_upsert_contact: [ + 'Upsert contact', + { text: 'for', field: 'email' }, + { text: 'or', field: 'linkedinUrl' }, + ], + monaco_delete_contact: [{ text: 'Delete contact', field: 'contactId', core: true }], + monaco_list_accounts: ['List accounts', { text: 'matching', field: 'filters' }], + monaco_get_account: [{ text: 'Get account', field: 'accountId', core: true }], + monaco_upsert_account: [ + 'Upsert account', + { text: 'for', field: 'domainLookup' }, + { text: 'named', field: 'name' }, + ], + monaco_update_account: [{ text: 'Update account', field: 'accountId', core: true }], + monaco_delete_account: [{ text: 'Delete account', field: 'accountId', core: true }], + monaco_list_opportunities: ['List opportunities', { text: 'matching', field: 'filters' }], + monaco_get_opportunity: [{ text: 'Get opportunity', field: 'opportunityId', core: true }], + monaco_create_opportunity: [ + { text: 'Create opportunity', field: 'name', core: true }, + { text: 'on account', field: 'accountId' }, + ], + monaco_update_opportunity: [ + { text: 'Update opportunity', field: 'opportunityId', core: true }, + { text: 'to stage', field: 'stage' }, + ], + monaco_delete_opportunity: [ + { text: 'Delete opportunity', field: 'opportunityId', core: true }, + ], + monaco_list_opportunity_contacts: [ + { text: 'List contacts on opportunity', field: 'opportunityId', core: true }, + ], + monaco_link_opportunity_contacts: [ + { text: 'Link', field: 'contactIds', core: true }, + { text: 'to opportunity', field: 'opportunityId', core: true }, + ], + monaco_unlink_opportunity_contacts: [ + { text: 'Unlink', field: 'contactIds', core: true }, + { text: 'from opportunity', field: 'opportunityId', core: true }, + ], + monaco_list_pipelines: ['List pipelines'], + monaco_list_tasks: ['List tasks', { text: 'matching', field: 'filters' }], + monaco_get_task: [{ text: 'Get task', field: 'taskId', core: true }], + monaco_create_task: [ + { text: 'Create task', field: 'taskTitle', core: true }, + { text: 'due', field: 'dueAt' }, + ], + monaco_update_task: [ + { text: 'Update task', field: 'taskId', core: true }, + { text: 'to', field: 'taskStatus' }, + ], + monaco_list_meetings: ['List meetings', { text: 'matching', field: 'filters' }], + monaco_get_meeting: [{ text: 'Get meeting', field: 'meetingId', core: true }], + monaco_list_messages: [ + 'List messages', + { text: 'for contact', field: 'contactId' }, + { text: 'for account', field: 'accountId' }, + ], + monaco_get_conversation: [ + { text: 'Get conversation', field: 'conversationId', core: true }, + { text: 'on', field: 'channel' }, + ], + monaco_list_tags: [{ text: 'List tags for', field: 'tagObject', core: true }], + monaco_create_tag: [ + { text: 'Create tag', field: 'tagName', core: true }, + { text: 'for', field: 'tagObject' }, + ], + monaco_list_users: ['List users', { text: 'matching', field: 'userQuery' }], + monaco_get_current_user: ['Get the authenticated user'], + monaco_get_field_schemas: [{ text: 'Get field schemas for', field: 'entity', core: true }], + monaco_list_sequences: ['List sequences', { text: 'matching', field: 'filters' }], + monaco_get_sequence: [{ text: 'Get sequence', field: 'sequenceId', core: true }], + monaco_update_sequence: [ + { text: 'Apply', field: 'sequenceAction', core: true }, + { text: 'to sequence', field: 'sequenceId', core: true }, + ], + monaco_list_audiences: ['List audiences'], + monaco_get_audience: [{ text: 'Get audience', field: 'audienceId', core: true }], + monaco_list_audience_contacts: [ + { text: 'List contacts in audience', field: 'audienceId', core: true }, + ], + monaco_create_audience: [{ text: 'Create audience', field: 'audienceName', core: true }], + monaco_add_audience_contacts: [ + { text: 'Add', field: 'contactIds', core: true }, + { text: 'to audience', field: 'audienceId', core: true }, + ], + monaco_remove_audience_contacts: [ + { text: 'Remove', field: 'contactIds', core: true }, + { text: 'from audience', field: 'audienceId', core: true }, + ], + monaco_list_campaigns: ['List campaigns', { text: 'matching', field: 'filters' }], + monaco_get_campaign: [{ text: 'Get campaign', field: 'campaignId', core: true }], + monaco_enroll_campaign_contacts: [ + { text: 'Enroll', field: 'contactIds', core: true }, + { text: 'in campaign', field: 'campaignId', core: true }, + ], + monaco_add_campaign_audiences: [ + { text: 'Add', field: 'audienceIds', core: true }, + { text: 'to campaign', field: 'campaignId', core: true }, + ], + }, + }, + }, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Contacts', id: 'monaco_list_contacts' }, + { label: 'Get Contact', id: 'monaco_get_contact' }, + { label: 'Create Contact', id: 'monaco_create_contact' }, + { label: 'Update Contact', id: 'monaco_update_contact' }, + { label: 'Upsert Contact', id: 'monaco_upsert_contact' }, + { label: 'Delete Contact', id: 'monaco_delete_contact' }, + { label: 'List Accounts', id: 'monaco_list_accounts' }, + { label: 'Get Account', id: 'monaco_get_account' }, + { label: 'Upsert Account', id: 'monaco_upsert_account' }, + { label: 'Update Account', id: 'monaco_update_account' }, + { label: 'Delete Account', id: 'monaco_delete_account' }, + { label: 'List Opportunities', id: 'monaco_list_opportunities' }, + { label: 'Get Opportunity', id: 'monaco_get_opportunity' }, + { label: 'Create Opportunity', id: 'monaco_create_opportunity' }, + { label: 'Update Opportunity', id: 'monaco_update_opportunity' }, + { label: 'Delete Opportunity', id: 'monaco_delete_opportunity' }, + { label: 'List Opportunity Contacts', id: 'monaco_list_opportunity_contacts' }, + { label: 'Link Contacts to Opportunity', id: 'monaco_link_opportunity_contacts' }, + { label: 'Unlink Contacts from Opportunity', id: 'monaco_unlink_opportunity_contacts' }, + { label: 'List Pipelines', id: 'monaco_list_pipelines' }, + { label: 'List Tasks', id: 'monaco_list_tasks' }, + { label: 'Get Task', id: 'monaco_get_task' }, + { label: 'Create Task', id: 'monaco_create_task' }, + { label: 'Update Task', id: 'monaco_update_task' }, + { label: 'List Meetings', id: 'monaco_list_meetings' }, + { label: 'Get Meeting', id: 'monaco_get_meeting' }, + { label: 'List Messages', id: 'monaco_list_messages' }, + { label: 'Get Conversation', id: 'monaco_get_conversation' }, + { label: 'List Tags', id: 'monaco_list_tags' }, + { label: 'Create Tag', id: 'monaco_create_tag' }, + { label: 'List Users', id: 'monaco_list_users' }, + { label: 'Get Current User', id: 'monaco_get_current_user' }, + { label: 'Get Field Schemas', id: 'monaco_get_field_schemas' }, + { label: 'List Sequences', id: 'monaco_list_sequences' }, + { label: 'Get Sequence', id: 'monaco_get_sequence' }, + { label: 'Update Sequence', id: 'monaco_update_sequence' }, + { label: 'List Audiences', id: 'monaco_list_audiences' }, + { label: 'Get Audience', id: 'monaco_get_audience' }, + { label: 'List Audience Contacts', id: 'monaco_list_audience_contacts' }, + { label: 'Create Audience', id: 'monaco_create_audience' }, + { label: 'Add Contacts to Audience', id: 'monaco_add_audience_contacts' }, + { label: 'Remove Contacts from Audience', id: 'monaco_remove_audience_contacts' }, + { label: 'List Campaigns', id: 'monaco_list_campaigns' }, + { label: 'Get Campaign', id: 'monaco_get_campaign' }, + { label: 'Enroll Contacts in Campaign', id: 'monaco_enroll_campaign_contacts' }, + { label: 'Add Audiences to Campaign', id: 'monaco_add_campaign_audiences' }, + ], + value: () => 'monaco_list_contacts', + }, + { + id: 'credential', + title: 'Monaco Account', + type: 'oauth-input', + serviceId: 'monaco', + credentialKind: 'service-account', + canonicalParamId: 'oauthCredential', + mode: 'basic', + placeholder: 'Select Monaco credential', + required: true, + }, + { + id: 'manualCredential', + title: 'Monaco Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + + { + id: 'contactId', + title: 'Contact ID', + type: 'short-input', + placeholder: 'Enter contact ID', + condition: ops( + 'monaco_get_contact', + 'monaco_update_contact', + 'monaco_upsert_contact', + 'monaco_delete_contact', + 'monaco_list_messages' + ), + required: ops('monaco_get_contact', 'monaco_update_contact', 'monaco_delete_contact'), + }, + { + id: 'accountId', + title: 'Account ID', + type: 'short-input', + placeholder: 'Enter account ID', + condition: ops( + 'monaco_get_account', + 'monaco_update_account', + 'monaco_upsert_account', + 'monaco_delete_account', + 'monaco_create_contact', + 'monaco_upsert_contact', + 'monaco_create_opportunity', + 'monaco_create_task', + 'monaco_update_task', + 'monaco_list_messages' + ), + required: ops( + 'monaco_get_account', + 'monaco_update_account', + 'monaco_delete_account', + 'monaco_create_opportunity' + ), + }, + { + id: 'opportunityId', + title: 'Opportunity ID', + type: 'short-input', + placeholder: 'Enter opportunity ID', + condition: ops( + 'monaco_get_opportunity', + 'monaco_update_opportunity', + 'monaco_delete_opportunity', + 'monaco_list_opportunity_contacts', + 'monaco_link_opportunity_contacts', + 'monaco_unlink_opportunity_contacts', + 'monaco_create_task' + ), + required: ops( + 'monaco_get_opportunity', + 'monaco_update_opportunity', + 'monaco_delete_opportunity', + 'monaco_list_opportunity_contacts', + 'monaco_link_opportunity_contacts', + 'monaco_unlink_opportunity_contacts' + ), + }, + { + id: 'taskId', + title: 'Task ID', + type: 'short-input', + placeholder: 'Enter task ID', + condition: ops('monaco_get_task', 'monaco_update_task'), + required: true, + }, + { + id: 'meetingId', + title: 'Meeting ID', + type: 'short-input', + placeholder: 'Enter meeting ID', + condition: ops('monaco_get_meeting'), + required: true, + }, + { + id: 'conversationId', + title: 'Conversation ID', + type: 'short-input', + placeholder: 'Email thread ID or LinkedIn chat ID from List Messages', + condition: ops('monaco_get_conversation'), + required: true, + }, + { + id: 'channel', + title: 'Channel', + type: 'dropdown', + options: [ + { label: 'Email', id: 'email' }, + { label: 'LinkedIn', id: 'linkedin' }, + ], + value: () => 'email', + condition: ops('monaco_get_conversation'), + required: true, + }, + { + id: 'sequenceId', + title: 'Sequence ID', + type: 'short-input', + placeholder: 'Enter sequence ID', + condition: ops('monaco_get_sequence', 'monaco_update_sequence'), + required: true, + }, + { + id: 'sequenceAction', + title: 'Action', + type: 'dropdown', + options: [ + { label: 'Pause', id: 'pause' }, + { label: 'Resume (resumes sending)', id: 'resume' }, + { label: 'Enqueue (queues for sending)', id: 'enqueue' }, + { label: 'Stop (ends as failed)', id: 'stop' }, + { label: 'Exclude (needs review or staged only)', id: 'exclude' }, + ], + value: () => 'pause', + condition: ops('monaco_update_sequence'), + required: true, + }, + { + id: 'audienceId', + title: 'Audience ID', + type: 'short-input', + placeholder: 'Enter audience ID', + condition: ops( + 'monaco_get_audience', + 'monaco_list_audience_contacts', + 'monaco_add_audience_contacts', + 'monaco_remove_audience_contacts' + ), + required: true, + }, + { + id: 'campaignId', + title: 'Campaign ID', + type: 'short-input', + placeholder: 'Enter campaign ID', + condition: ops( + 'monaco_get_campaign', + 'monaco_enroll_campaign_contacts', + 'monaco_add_campaign_audiences' + ), + required: true, + }, + { + id: 'audienceName', + title: 'Audience Name', + type: 'short-input', + placeholder: 'Enter a unique audience name', + condition: ops('monaco_create_audience', 'monaco_enroll_campaign_contacts'), + required: true, + }, + { + id: 'contactIds', + title: 'Contact IDs', + type: 'long-input', + placeholder: 'Comma-separated contact IDs or a JSON array', + condition: ops( + 'monaco_link_opportunity_contacts', + 'monaco_unlink_opportunity_contacts', + 'monaco_create_audience', + 'monaco_add_audience_contacts', + 'monaco_remove_audience_contacts', + 'monaco_enroll_campaign_contacts' + ), + required: ops( + 'monaco_link_opportunity_contacts', + 'monaco_unlink_opportunity_contacts', + 'monaco_add_audience_contacts', + 'monaco_remove_audience_contacts', + 'monaco_enroll_campaign_contacts' + ), + wandConfig: ID_LIST_WAND('Monaco contact IDs'), + }, + { + id: 'audienceIds', + title: 'Audience IDs', + type: 'long-input', + placeholder: 'Comma-separated audience IDs or a JSON array', + condition: ops('monaco_add_campaign_audiences'), + required: true, + wandConfig: ID_LIST_WAND('Monaco audience IDs'), + }, + { + id: 'audienceFilters', + title: 'Contact Filters', + type: 'code', + language: 'json', + placeholder: '[{"field": "title", "condition": "contains", "value": "VP"}]', + condition: ops('monaco_create_audience'), + wandConfig: FILTERS_WAND, + }, + { + id: 'excludedIds', + title: 'Excluded Contact IDs', + type: 'long-input', + placeholder: 'Contact IDs to leave out of the filtered set', + condition: ops('monaco_create_audience'), + mode: 'advanced', + wandConfig: ID_LIST_WAND('Monaco contact IDs to exclude'), + }, + { + id: 'sourceAudienceId', + title: 'Source Audience ID', + type: 'short-input', + placeholder: 'Only match contacts already in this audience', + condition: ops('monaco_create_audience'), + mode: 'advanced', + }, + + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'name@company.com', + condition: ops(...CONTACT_WRITE_OPERATIONS), + }, + { + id: 'linkedinUrl', + title: 'LinkedIn URL', + type: 'short-input', + placeholder: 'https://www.linkedin.com/in/...', + condition: ops(...CONTACT_WRITE_OPERATIONS, ...ACCOUNT_WRITE_OPERATIONS), + }, + { + id: 'firstName', + title: 'First Name', + type: 'short-input', + placeholder: 'Enter first name', + condition: ops(...CONTACT_WRITE_OPERATIONS), + }, + { + id: 'lastName', + title: 'Last Name', + type: 'short-input', + placeholder: 'Enter last name', + condition: ops(...CONTACT_WRITE_OPERATIONS), + }, + { + id: 'title', + title: 'Job Title', + type: 'short-input', + placeholder: 'Enter job title', + condition: ops(...CONTACT_WRITE_OPERATIONS), + }, + { + id: 'phoneNumber', + title: 'Phone Number', + type: 'short-input', + placeholder: '+1 555 0100', + condition: ops(...CONTACT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'domain', + title: 'Company Domain', + type: 'short-input', + placeholder: 'company.com', + condition: ops('monaco_create_contact', 'monaco_upsert_contact'), + mode: 'advanced', + }, + { + id: 'country', + title: 'Country', + type: 'short-input', + placeholder: 'Country name or code', + condition: ops('monaco_update_contact'), + mode: 'advanced', + }, + { + id: 'jobStartDate', + title: 'Job Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: ops('monaco_update_contact'), + mode: 'advanced', + wandConfig: DATE_WAND("contact's current job start date"), + }, + { + id: 'doNotContact', + title: 'Do Not Contact', + type: 'dropdown', + options: BOOLEAN_CHOICES, + value: () => '', + condition: ops(...CONTACT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'isInbound', + title: 'Inbound Lead', + type: 'switch', + condition: ops('monaco_create_contact', 'monaco_upsert_contact'), + mode: 'advanced', + }, + + { + id: 'domainLookup', + title: 'Domain', + type: 'short-input', + placeholder: 'company.com (finds or creates the account)', + condition: ops('monaco_upsert_account'), + }, + { + id: 'name', + title: 'Name', + type: 'short-input', + placeholder: 'Enter name', + condition: ops(...ACCOUNT_WRITE_OPERATIONS, ...OPPORTUNITY_WRITE_OPERATIONS), + required: ops('monaco_create_opportunity'), + }, + { + id: 'accountStatus', + title: 'Account Status', + type: 'short-input', + placeholder: + 'NEW, PROSPECTING, OPPORTUNITY, CUSTOMER, DISQUALIFIED, NURTURE, INBOUND, CHURNED', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + }, + { + id: 'owner', + title: 'Owner User ID', + type: 'short-input', + placeholder: 'User ID from List Users', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'parentAccount', + title: 'Parent Account ID', + type: 'short-input', + placeholder: 'ID of a top-level account', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'companyId', + title: 'Company ID', + type: 'short-input', + placeholder: 'ID of the company to link', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'replaceEditedFields', + title: 'Replace Edited Fields', + type: 'switch', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'companyDescription', + title: 'Company Description', + type: 'long-input', + placeholder: 'Describe the company', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'employeeCount', + title: 'Employee Count', + type: 'short-input', + placeholder: '250', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'foundedYear', + title: 'Founded Year', + type: 'short-input', + placeholder: '2019', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'estimatedAnnualRevenueUsd', + title: 'Estimated Annual Revenue (USD)', + type: 'short-input', + placeholder: '5000000', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'totalFundingUsd', + title: 'Total Funding (USD)', + type: 'short-input', + placeholder: '12000000', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'lastFundingRound', + title: 'Last Funding Round', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + ...[ + 'pre_seed', + 'seed', + 'angel', + 'series_a', + 'series_b', + 'series_c', + 'series_d', + 'series_e', + 'series_f', + 'series_g', + 'series_h', + 'series_i', + 'series_j', + 'series_unknown', + 'private_equity', + 'debt_financing', + 'convertible_note', + 'grant', + 'non_equity_assistance', + 'corporate_round', + 'equity_crowdfunding', + 'product_crowdfunding', + 'initial_coin_offering', + 'secondary_market', + 'post_ipo_equity', + 'post_ipo_debt', + 'post_ipo_secondary', + 'undisclosed', + ].map((round) => ({ label: round, id: round })), + ], + value: () => '', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'lastFundingDate', + title: 'Last Funding Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + wandConfig: DATE_WAND('date of the most recent funding round'), + }, + { + id: 'industries', + title: 'Industries', + type: 'short-input', + placeholder: 'Comma-separated industries', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'businessModel', + title: 'Business Model', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'B2B', id: 'B2B' }, + { label: 'B2C', id: 'B2C' }, + ], + value: () => '', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'companyStatus', + title: 'Company Status', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Active', id: 'active' }, + { label: 'Acquired', id: 'acquired' }, + { label: 'Out of Business', id: 'out_of_business' }, + { label: 'IPO', id: 'ipo' }, + { label: 'Unknown', id: 'unknown' }, + ], + value: () => '', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'domains', + title: 'Domains', + type: 'short-input', + placeholder: 'Complete domain list, primary first (replaces existing)', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'resetFields', + title: 'Reset Company Fields', + type: 'short-input', + placeholder: 'e.g. description, employee_count', + condition: ops(...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'location', + title: 'Location', + type: 'short-input', + placeholder: 'City, state, country', + condition: ops(...CONTACT_WRITE_OPERATIONS, ...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + + { + id: 'pipelineId', + title: 'Pipeline ID', + type: 'short-input', + placeholder: 'Pipeline ID from List Pipelines (defaults to the default pipeline)', + condition: ops('monaco_list_opportunities', ...OPPORTUNITY_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'stage', + title: 'Stage', + type: 'short-input', + placeholder: 'Stage key from List Pipelines', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + }, + { + id: 'stageId', + title: 'Stage ID', + type: 'short-input', + placeholder: 'Stage ID from List Pipelines, instead of a stage key', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'amount', + title: 'Amount', + type: 'short-input', + placeholder: '50000 (set together with currency)', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + }, + { + id: 'currencyCode', + title: 'Currency', + type: 'short-input', + placeholder: 'USD (set together with amount)', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + }, + { + id: 'estimatedCloseDate', + title: 'Estimated Close Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + wandConfig: DATE_WAND('expected close date'), + }, + { + id: 'actualCloseDate', + title: 'Actual Close Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: ops('monaco_update_opportunity'), + mode: 'advanced', + wandConfig: DATE_WAND('date the opportunity closed'), + }, + { + id: 'ownerUserId', + title: 'Owner User ID', + type: 'short-input', + placeholder: 'User ID from List Users', + condition: ops(...OPPORTUNITY_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'idempotencyKey', + title: 'Idempotency Key', + type: 'short-input', + placeholder: 'UUID that prevents duplicate creation', + condition: ops('monaco_create_opportunity'), + mode: 'advanced', + }, + + { + id: 'taskTitle', + title: 'Task Title', + type: 'short-input', + placeholder: 'Enter task title', + condition: ops(...TASK_WRITE_OPERATIONS), + required: ops('monaco_create_task'), + }, + { + id: 'taskDescription', + title: 'Task Description', + type: 'long-input', + placeholder: 'Describe the task', + condition: ops(...TASK_WRITE_OPERATIONS), + }, + { + id: 'taskType', + title: 'Task Type', + type: 'dropdown', + options: [ + { label: 'Default (General Action Item)', id: '' }, + { label: 'General Action Item', id: 'GENERAL_ACTION_ITEM' }, + { label: 'Email', id: 'EMAIL' }, + ], + value: () => '', + condition: ops('monaco_create_task'), + }, + { + id: 'taskStatus', + title: 'Task Status', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'To Do', id: 'TODO' }, + { label: 'Done', id: 'DONE' }, + { label: 'Cancelled', id: 'CANCELLED' }, + ], + value: () => '', + condition: ops('monaco_update_task'), + }, + { + id: 'dueAt', + title: 'Due At', + type: 'short-input', + placeholder: '2026-01-15T17:00:00Z', + condition: ops(...TASK_WRITE_OPERATIONS), + wandConfig: { + enabled: true, + placeholder: 'Describe when the task is due, e.g. "Friday at 5pm Pacific"', + prompt: + "Generate an ISO 8601 date-time for when the task is due, based on the user's description. Return ONLY the timestamp string.", + generationType: 'timestamp', + }, + }, + { + id: 'assigneeId', + title: 'Assignee User ID', + type: 'short-input', + placeholder: 'User ID from List Users (defaults to the API key owner)', + condition: ops(...TASK_WRITE_OPERATIONS), + mode: 'advanced', + }, + { + id: 'messageDraft', + title: 'Email Draft', + type: 'code', + language: 'json', + placeholder: + '{"recipients": [{"name": "Ada", "email": "ada@acme.com"}], "subject": "Follow up", "body": "Hi Ada,"}', + condition: ops(...TASK_WRITE_OPERATIONS), + mode: 'advanced', + wandConfig: { + enabled: true, + placeholder: 'Describe the email to draft and who it goes to', + prompt: + 'Generate a Monaco email task draft as {"recipients": [{"name", "email"}], "cc": [{"name", "email"}], "subject", "body"}. recipients, subject, and body are required; cc is optional; body may be plain text or HTML. Return ONLY the JSON object.', + generationType: 'json-object', + }, + }, + + { + id: 'notes', + title: 'Notes', + type: 'long-input', + placeholder: 'Enter notes', + condition: ops( + ...CONTACT_WRITE_OPERATIONS, + ...ACCOUNT_WRITE_OPERATIONS, + ...OPPORTUNITY_WRITE_OPERATIONS + ), + mode: 'advanced', + }, + { + id: 'tags', + title: 'Tag IDs', + type: 'short-input', + placeholder: 'Comma-separated tag IDs from List Tags', + condition: ops( + ...CONTACT_WRITE_OPERATIONS, + ...ACCOUNT_WRITE_OPERATIONS, + ...OPPORTUNITY_WRITE_OPERATIONS + ), + mode: 'advanced', + }, + { + id: 'customFields', + title: 'Custom Fields', + type: 'code', + language: 'json', + placeholder: '{"custom_field_": "value"}', + condition: ops( + ...CONTACT_WRITE_OPERATIONS, + ...ACCOUNT_WRITE_OPERATIONS, + ...OPPORTUNITY_WRITE_OPERATIONS + ), + mode: 'advanced', + wandConfig: { + enabled: true, + placeholder: 'Describe the custom field values to set', + prompt: + 'Generate a JSON object of Monaco custom field values keyed by their full custom_field_ keys from Get Field Schemas. Return ONLY the JSON object.', + generationType: 'json-object', + }, + }, + { + id: 'clearFields', + title: 'Clear Fields', + type: 'short-input', + placeholder: 'Comma-separated field keys to clear, e.g. title, notes', + condition: ops('monaco_update_contact', 'monaco_upsert_contact', ...ACCOUNT_WRITE_OPERATIONS), + mode: 'advanced', + }, + + { + id: 'tagObject', + title: 'Object Type', + type: 'dropdown', + options: [ + { label: 'Contacts', id: 'contacts' }, + { label: 'Accounts', id: 'accounts' }, + { label: 'Opportunities', id: 'opportunities' }, + ], + value: () => 'contacts', + condition: ops('monaco_list_tags', 'monaco_create_tag'), + required: true, + }, + { + id: 'tagName', + title: 'Tag Name', + type: 'short-input', + placeholder: 'Letters, numbers, and spaces only', + condition: ops('monaco_create_tag'), + required: true, + }, + { + id: 'tagColor', + title: 'Color', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + ...['green', 'blue', 'red', 'yellow', 'purple', 'berry', 'peach', 'gray'].map((color) => ({ + label: color.charAt(0).toUpperCase() + color.slice(1), + id: color, + })), + ], + value: () => '', + condition: ops('monaco_create_tag'), + }, + { + id: 'userQuery', + title: 'Search', + type: 'short-input', + placeholder: 'Filter by name or email', + condition: ops('monaco_list_users'), + }, + { + id: 'entity', + title: 'Entity', + type: 'dropdown', + options: [ + { label: 'Contacts', id: 'contacts' }, + { label: 'Accounts', id: 'accounts' }, + { label: 'Opportunities', id: 'opportunities' }, + { label: 'Tasks', id: 'tasks' }, + { label: 'Meetings', id: 'meetings' }, + { label: 'Sequences', id: 'sequences' }, + ], + value: () => 'contacts', + condition: ops('monaco_get_field_schemas'), + required: true, + }, + { + id: 'channels', + title: 'Channels', + type: 'short-input', + placeholder: 'email, linkedin (defaults to both)', + condition: ops('monaco_list_messages'), + mode: 'advanced', + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'nextCursor from the previous page', + condition: ops('monaco_list_messages'), + mode: 'advanced', + }, + + { + id: 'filters', + title: 'Filters', + type: 'code', + language: 'json', + placeholder: + '[{"field": "created_at", "condition": "is_after", "value": "2026-01-01T00:00:00Z"}]', + condition: ops(...LIST_OPERATIONS), + wandConfig: FILTERS_WAND, + }, + { + id: 'sort', + title: 'Sort', + type: 'short-input', + placeholder: '-created_at (meetings default to -start_time)', + condition: ops(...LIST_OPERATIONS), + mode: 'advanced', + }, + { + id: 'includeCustomFields', + title: 'Include Custom Fields', + type: 'dropdown', + options: BOOLEAN_CHOICES, + value: () => '', + condition: ops('monaco_list_contacts', 'monaco_list_accounts', 'monaco_list_opportunities'), + mode: 'advanced', + }, + { + id: 'includeReasoning', + title: 'Include Custom Field Reasoning', + type: 'switch', + condition: ops('monaco_get_contact', 'monaco_get_account', 'monaco_get_opportunity'), + mode: 'advanced', + }, + { + id: 'page', + title: 'Page', + type: 'short-input', + placeholder: '1', + condition: ops(...PAGED_OPERATIONS), + mode: 'advanced', + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: 'Defaults to the endpoint page size', + condition: ops(...PAGED_OPERATIONS, 'monaco_list_messages'), + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'monaco_list_contacts', + 'monaco_get_contact', + 'monaco_create_contact', + 'monaco_update_contact', + 'monaco_upsert_contact', + 'monaco_delete_contact', + 'monaco_list_accounts', + 'monaco_get_account', + 'monaco_upsert_account', + 'monaco_update_account', + 'monaco_delete_account', + 'monaco_list_opportunities', + 'monaco_get_opportunity', + 'monaco_create_opportunity', + 'monaco_update_opportunity', + 'monaco_delete_opportunity', + 'monaco_list_opportunity_contacts', + 'monaco_link_opportunity_contacts', + 'monaco_unlink_opportunity_contacts', + 'monaco_list_pipelines', + 'monaco_list_tasks', + 'monaco_get_task', + 'monaco_create_task', + 'monaco_update_task', + 'monaco_list_meetings', + 'monaco_get_meeting', + 'monaco_list_messages', + 'monaco_get_conversation', + 'monaco_list_tags', + 'monaco_create_tag', + 'monaco_list_users', + 'monaco_get_current_user', + 'monaco_get_field_schemas', + 'monaco_list_sequences', + 'monaco_get_sequence', + 'monaco_update_sequence', + 'monaco_list_audiences', + 'monaco_get_audience', + 'monaco_list_audience_contacts', + 'monaco_create_audience', + 'monaco_add_audience_contacts', + 'monaco_remove_audience_contacts', + 'monaco_list_campaigns', + 'monaco_get_campaign', + 'monaco_enroll_campaign_contacts', + 'monaco_add_campaign_audiences', + ], + config: { + tool: (params) => { + if (!isMonacoOperation(params.operation)) { + throw new Error(`Invalid Monaco operation: ${String(params.operation)}`) + } + return params.operation + }, + /** + * Workflow runs pass no context and read subBlock values. Agent tool calls pass the + * operation in `context` and supply arguments under tool param names, so a param whose + * subBlock has a different id falls back to the model's value. + */ + params: (params, context) => { + const operation = context?.operation ?? params.operation + const result: Record = { + operation: undefined, + credential: undefined, + manualCredential: undefined, + oauthCredential: params.oauthCredential, + } + for (const key of ALL_FIELD_KEYS) result[key] = undefined + if (!isMonacoOperation(operation)) return result + const bindings: readonly FieldBinding[] = OPERATION_FIELDS[operation] + for (const [param, field, kind] of bindings) { + const value = isBlank(params[field]) && context?.operation ? params[param] : params[field] + result[param] = coerceField(value, kind, param) + } + return result + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Monaco operation to perform' }, + oauthCredential: { type: 'string', description: 'Saved Monaco API key credential' }, + contactId: { type: 'string', description: 'Contact ID' }, + accountId: { type: 'string', description: 'Account ID' }, + opportunityId: { type: 'string', description: 'Opportunity ID' }, + taskId: { type: 'string', description: 'Task ID' }, + meetingId: { type: 'string', description: 'Meeting ID' }, + conversationId: { type: 'string', description: 'Email thread ID or LinkedIn chat ID' }, + channel: { type: 'string', description: 'Conversation channel (email or linkedin)' }, + sequenceId: { type: 'string', description: 'Sequence ID' }, + sequenceAction: { + type: 'string', + description: 'Sequence state transition (pause, resume, enqueue, stop, exclude)', + }, + audienceId: { type: 'string', description: 'Audience ID' }, + campaignId: { type: 'string', description: 'Campaign ID' }, + audienceName: { type: 'string', description: 'Name of the audience to create' }, + contactIds: { type: 'json', description: 'Contact IDs, as an array or comma-separated list' }, + audienceIds: { + type: 'json', + description: 'Audience IDs, as an array or comma-separated list', + }, + audienceFilters: { type: 'json', description: 'Contact filters that seed a new audience' }, + excludedIds: { type: 'json', description: 'Contact IDs to exclude from the filtered set' }, + sourceAudienceId: { + type: 'string', + description: 'Audience whose members the filters are restricted to', + }, + email: { type: 'string', description: 'Contact email address' }, + linkedinUrl: { type: 'string', description: 'LinkedIn profile or company page URL' }, + firstName: { type: 'string', description: 'Contact first name' }, + lastName: { type: 'string', description: 'Contact last name' }, + title: { type: 'string', description: 'Contact job title' }, + phoneNumber: { type: 'string', description: 'Contact phone number' }, + domain: { type: 'string', description: "Domain of the contact's company" }, + country: { type: 'string', description: 'Contact country' }, + jobStartDate: { type: 'string', description: "Start date of the contact's current job" }, + doNotContact: { type: 'string', description: 'Whether the contact opted out of outreach' }, + isInbound: { type: 'boolean', description: 'Whether the contact is an inbound lead' }, + domainLookup: { type: 'string', description: 'Domain used to find or create the account' }, + name: { type: 'string', description: 'Account or opportunity name' }, + accountStatus: { type: 'string', description: 'Account status' }, + owner: { type: 'string', description: 'Account owner user ID' }, + parentAccount: { type: 'string', description: 'Parent account ID' }, + companyId: { type: 'string', description: 'Company ID to link to the account' }, + replaceEditedFields: { + type: 'boolean', + description: 'Replace user-edited fields when linking a company', + }, + companyDescription: { type: 'string', description: 'Company description' }, + employeeCount: { type: 'number', description: 'Number of employees' }, + foundedYear: { type: 'number', description: 'Year the company was founded' }, + estimatedAnnualRevenueUsd: { type: 'number', description: 'Estimated annual revenue in USD' }, + totalFundingUsd: { type: 'number', description: 'Total funding raised in USD' }, + lastFundingRound: { type: 'string', description: 'Most recent funding round type' }, + lastFundingDate: { type: 'string', description: 'Date of the most recent funding round' }, + industries: { type: 'json', description: 'Industries, as an array or comma-separated list' }, + businessModel: { type: 'string', description: 'Business model (B2B or B2C)' }, + companyStatus: { type: 'string', description: 'Company operating status' }, + domains: { type: 'json', description: "The account's complete domain list" }, + resetFields: { type: 'json', description: 'Company fields to reset to the linked company' }, + location: { type: 'string', description: 'Contact location or account headquarters' }, + pipelineId: { type: 'string', description: 'Pipeline ID' }, + stage: { type: 'string', description: 'Opportunity stage key' }, + stageId: { type: 'string', description: 'Opportunity stage ID' }, + amount: { type: 'number', description: 'Opportunity estimated value amount' }, + currencyCode: { type: 'string', description: 'ISO 4217 currency code' }, + estimatedCloseDate: { type: 'string', description: 'Expected close date (YYYY-MM-DD)' }, + actualCloseDate: { type: 'string', description: 'Actual close date (YYYY-MM-DD)' }, + ownerUserId: { type: 'string', description: 'Opportunity owner user ID' }, + idempotencyKey: { type: 'string', description: 'Idempotency key for opportunity creation' }, + taskTitle: { type: 'string', description: 'Task title' }, + taskDescription: { type: 'string', description: 'Task description' }, + taskType: { type: 'string', description: 'Task type (GENERAL_ACTION_ITEM or EMAIL)' }, + taskStatus: { type: 'string', description: 'Task status (TODO, DONE, CANCELLED)' }, + dueAt: { type: 'string', description: 'Task due date and time (ISO 8601)' }, + assigneeId: { type: 'string', description: 'Task assignee user ID' }, + messageDraft: { type: 'json', description: 'Email draft for an EMAIL task' }, + notes: { type: 'string', description: 'Notes on the record' }, + tags: { type: 'json', description: 'Tag IDs, as an array or comma-separated list' }, + customFields: { type: 'json', description: 'Custom field values keyed by custom_field_' }, + clearFields: { type: 'json', description: 'Field keys to clear' }, + tagObject: { type: 'string', description: 'Object type for tags' }, + tagName: { type: 'string', description: 'Tag name' }, + tagColor: { type: 'string', description: 'Tag color' }, + userQuery: { type: 'string', description: 'Name or email substring to filter users' }, + entity: { type: 'string', description: 'Entity to describe field schemas for' }, + channels: { type: 'json', description: 'Message channels to include' }, + cursor: { type: 'string', description: 'Message feed pagination cursor' }, + filters: { type: 'json', description: 'List filters' }, + sort: { type: 'string', description: 'Sort field, prefixed with - for descending' }, + includeCustomFields: { type: 'string', description: 'Include custom fields on list items' }, + includeReasoning: { type: 'boolean', description: 'Return custom field reasoning' }, + page: { type: 'number', description: 'Page number' }, + pageSize: { type: 'number', description: 'Results per page' }, + }, + + outputs: { + contacts: { + type: 'json', + description: + 'Contacts [{id, account_id, account_name, first_name, last_name, email, title, phone_number, linkedin_url, location, country, job_start_date, source, do_not_contact, notes, scoring, tags, created_at, updated_at, last_activity_at, last_activity_type, last_outbound_at, last_outbound_type, last_inbound_at, last_inbound_type, custom_fields}]; List Opportunity Contacts returns links instead [{contact_id, source, created_at, email, first_name, last_name, title, account_id}]', + condition: ops( + 'monaco_list_contacts', + 'monaco_list_audience_contacts', + 'monaco_list_opportunity_contacts' + ), + }, + contact: { + type: 'json', + description: + 'Contact {id, account_id, account_name, first_name, last_name, email, title, phone_number, linkedin_url, location, country, job_start_date, source, do_not_contact, notes, scoring, tags, created_at, updated_at, last_activity_at, last_activity_type, last_outbound_at, last_outbound_type, last_inbound_at, last_inbound_type, custom_fields}; Get Contact adds heat_score_explanation and Create/Upsert add enrichment_status', + condition: ops( + 'monaco_get_contact', + 'monaco_create_contact', + 'monaco_update_contact', + 'monaco_upsert_contact' + ), + }, + accounts: { + type: 'json', + description: + 'Accounts [{id, name, status, owner, parent_account, notes, domains, company, overridden_fields, scoring, tags, created_at, updated_at, last_activity_at, last_activity_type, last_contacted_at, last_contacted_type, last_inbound_at, last_inbound_type, custom_fields}]', + condition: ops('monaco_list_accounts'), + }, + account: { + type: 'json', + description: + 'Account with the same fields as accounts; Get Account adds heat_score_explanation and Upsert/Update add skipped_domains', + condition: ops('monaco_get_account', 'monaco_upsert_account', 'monaco_update_account'), + }, + opportunities: { + type: 'json', + description: + 'Opportunities [{id, account_id, name, owner, stage, stage_id, pipeline_id, pipeline_name, estimated_value, estimated_close_date, actual_close_date, summary, risks, notes, tags, contact_ids, last_activity_at, updated_at, created_at, custom_fields}]', + condition: ops('monaco_list_opportunities'), + }, + opportunity: { + type: 'json', + description: 'Opportunity with the same fields as opportunities', + condition: ops( + 'monaco_get_opportunity', + 'monaco_create_opportunity', + 'monaco_update_opportunity' + ), + }, + pipelines: { + type: 'json', + description: 'Pipelines [{id, name, is_default, stages}]', + condition: ops('monaco_list_pipelines'), + }, + tasks: { + type: 'json', + description: + 'Tasks [{id, account_id, opportunity_id, title, description, task_type, assignee, status, due_at, completed_at, created_at, updated_at, message_draft}]; message_draft is always null in list results', + condition: ops('monaco_list_tasks'), + }, + task: { + type: 'json', + description: + 'Task {id, account_id, opportunity_id, title, description, task_type, assignee, status, due_at, completed_at, created_at, updated_at, message_draft}', + condition: ops('monaco_get_task', 'monaco_create_task', 'monaco_update_task'), + }, + meetings: { + type: 'json', + description: + 'Meetings [{id, account_id, account_candidates, opportunity_id, opportunity_candidates, title, description, start_time, end_time, occurred_at, meeting_platform, meeting_url, organizer, attendees, next_actions, created_at, updated_at}]', + condition: ops('monaco_list_meetings'), + }, + meeting: { + type: 'json', + description: + 'Meeting with the list fields plus ai_premeeting_notes, meeting_summary, video_url, transcript_source, and transcript (speaker-attributed segments; can be very large)', + condition: ops('monaco_get_meeting'), + }, + messages: { + type: 'json', + description: + 'Messages [{conversation_id, channel, direction, subject, preview, timestamp, contact_id, account_id}], most recent first', + condition: ops('monaco_list_messages'), + }, + nextCursor: { + type: 'string', + description: 'Cursor for the next page of messages, or null on the last page', + condition: ops('monaco_list_messages'), + }, + hasMore: { + type: 'boolean', + description: 'Whether more messages are available', + condition: ops('monaco_list_messages'), + }, + conversation: { + type: 'json', + description: + 'Conversation {id, channel, subject, participants, account_id, account_candidates, messages, message_pagination}', + condition: ops('monaco_get_conversation'), + }, + tags: { + type: 'json', + description: 'Tags [{id, name, color}]', + condition: ops('monaco_list_tags'), + }, + tag: { + type: 'json', + description: 'The created tag {id, name, color}', + condition: ops('monaco_create_tag'), + }, + users: { + type: 'json', + description: 'Users [{id, first_name, last_name, email, role}]', + condition: ops('monaco_list_users'), + }, + currentUser: { + type: 'json', + description: + 'Authenticated user {org_id, user_id, user_email, user_first_name, user_last_name, org_name}', + condition: ops('monaco_get_current_user'), + }, + fields: { + type: 'json', + description: + 'Field schemas [{name, key, type, parent, filterable, sortable, updatable, allowed_operators, custom_field_settings, enum_field_settings}]', + condition: ops('monaco_get_field_schemas'), + }, + sequences: { + type: 'json', + description: + 'Sequences [{id, contact_id, name, description, status, template_id, created_at, updated_at, last_interaction, enqueued_at, started_at, responded_at, meeting_booked_at, sender_id, sender_name, exclusion_info}]', + condition: ops('monaco_list_sequences'), + }, + sequence: { + type: 'json', + description: 'Sequence with the list fields plus its ordered tasks (steps)', + condition: ops('monaco_get_sequence', 'monaco_update_sequence'), + }, + audiences: { + type: 'json', + description: 'Audiences [{id, name, type, status, contact_count, created_at, updated_at}]', + condition: ops('monaco_list_audiences'), + }, + audience: { + type: 'json', + description: 'Audience {id, name, type, status, contact_count, created_at, updated_at}', + condition: ops('monaco_get_audience', 'monaco_create_audience'), + }, + campaigns: { + type: 'json', + description: + 'Campaigns [{id, name, status, prioritized, end_date, template_id, template_name, audiences, enrollment_status, created_at, updated_at}]', + condition: ops('monaco_list_campaigns'), + }, + campaign: { + type: 'json', + description: + 'Campaign with the list fields plus distribution, rules_of_engagement, autopilot_mode, autopilot_settings, and contact_readiness_settings', + condition: ops('monaco_get_campaign', 'monaco_add_campaign_audiences'), + }, + pagination: { + type: 'json', + description: 'Pagination {page, page_size, total_count, total_pages}', + condition: ops(...PAGED_OPERATIONS.filter((op) => op !== 'monaco_get_conversation')), + }, + id: { + type: 'string', + description: 'ID of the deleted record', + condition: ops('monaco_delete_contact', 'monaco_delete_account', 'monaco_delete_opportunity'), + }, + deleted: { + type: 'boolean', + description: 'Whether Monaco deleted the record', + condition: ops('monaco_delete_contact', 'monaco_delete_account', 'monaco_delete_opportunity'), + }, + opportunityId: { + type: 'string', + description: 'ID of the opportunity the contacts were linked to or unlinked from', + condition: ops('monaco_link_opportunity_contacts', 'monaco_unlink_opportunity_contacts'), + }, + contactIds: { + type: 'json', + description: 'Contact IDs that were linked or unlinked', + condition: ops('monaco_link_opportunity_contacts', 'monaco_unlink_opportunity_contacts'), + }, + audienceId: { + type: 'string', + description: 'ID of the audience that was changed', + condition: ops('monaco_add_audience_contacts', 'monaco_remove_audience_contacts'), + }, + addedCount: { + type: 'number', + description: 'Number of contacts added to the audience', + condition: ops('monaco_add_audience_contacts'), + }, + unknownContactIds: { + type: 'json', + description: 'Contact IDs Monaco did not recognize', + condition: ops('monaco_add_audience_contacts'), + }, + failures: { + type: 'json', + description: 'Per-contact failures [{reason, contact_ids}]', + condition: ops('monaco_add_audience_contacts', 'monaco_remove_audience_contacts'), + }, + successCount: { + type: 'number', + description: 'Number of contacts enrolled', + condition: ops('monaco_enroll_campaign_contacts'), + }, + failureCount: { + type: 'number', + description: 'Number of contacts that could not be enrolled', + condition: ops('monaco_enroll_campaign_contacts'), + }, + failureReasons: { + type: 'json', + description: 'Enrollment failure reasons [{code, description, count}]', + condition: ops('monaco_enroll_campaign_contacts'), + }, + }, +} + +export const MonacoBlockMeta = { + tags: ['sales-engagement', 'automation', 'agentic'], + url: 'https://www.monaco.com', + templates: [ + { + icon: MonacoIcon, + title: 'Monaco segment outbound', + prompt: + 'Build a chat workflow that turns a request like "VPs of Sales at Series B fintechs" into Monaco contact filters, lists the matching contacts for my approval, and enrolls the approved contacts in the campaign I name, then reports how many were enrolled and why any failed.', + modules: ['agent', 'workflows'], + category: 'sales', + tags: ['sales', 'outreach', 'automation'], + featured: true, + }, + { + icon: MonacoIcon, + title: 'Monaco meeting prep brief', + prompt: + "Create a scheduled workflow that runs each morning, lists today's Monaco meetings, and for each one pulls the account, its open opportunities, and recent email and LinkedIn messages, then posts a prep brief for the meeting owner to Slack.", + modules: ['scheduled', 'agent', 'workflows'], + category: 'sales', + tags: ['sales', 'meetings', 'research'], + alsoIntegrations: ['slack'], + }, + { + icon: MonacoIcon, + title: 'Monaco pipeline hygiene', + prompt: + 'Build a weekly workflow that finds Monaco opportunities whose estimated close date has passed while they are still open, writes them to a table with owner and stage, and creates a follow-up task for each owner to update or close the deal.', + modules: ['scheduled', 'tables', 'workflows'], + category: 'sales', + tags: ['sales', 'reporting', 'automation'], + }, + { + icon: MonacoIcon, + title: 'Monaco meeting follow-ups', + prompt: + "Create a workflow that reads a finished Monaco meeting's summary, next actions, and transcript, drafts a follow-up email task for the attendees, and records the agreed next step in the opportunity notes.", + modules: ['agent', 'workflows'], + category: 'sales', + tags: ['sales', 'meetings', 'productivity'], + }, + { + icon: MonacoIcon, + title: 'Monaco inbound lead capture', + prompt: + 'Build a workflow that takes form submissions, upserts each person as an inbound Monaco contact on the account for their email domain, tags them by source, and opens a task for the account owner to respond within a day.', + modules: ['workflows'], + category: 'sales', + tags: ['sales', 'lead-generation', 'automation'], + }, + { + icon: MonacoIcon, + title: 'Monaco revenue dashboard', + prompt: + 'Create a scheduled workflow that pages through every Monaco opportunity with its pipeline stages, totals open pipeline value by pipeline and stage, sums estimated value by expected close month, and writes the results to a table for a weekly revenue review.', + modules: ['scheduled', 'tables', 'agent'], + category: 'sales', + tags: ['sales', 'reporting', 'analytics'], + }, + { + icon: MonacoIcon, + title: 'Monaco CRM enrichment sync', + prompt: + 'Build a workflow that reads companies from a table, upserts each one as a Monaco account by domain with its industry, employee count, and funding details, and records the resulting account IDs and any skipped domains back in the table.', + modules: ['tables', 'workflows'], + category: 'sales', + tags: ['sales', 'enrichment', 'automation'], + }, + { + icon: MonacoIcon, + title: 'Monaco sequence review', + prompt: + 'Create an agent that lists Monaco sequences waiting for review, reads each one with its steps and the contact it targets, and excludes the ones that look off-target after the sales lead approves, summarizing each decision.', + modules: ['agent', 'workflows'], + category: 'sales', + tags: ['sales', 'outreach', 'review'], + }, + ], + skills: [ + { + name: 'build-outbound-segment', + description: + 'Turn a target segment description into a Monaco audience and attach it to a campaign.', + content: + '# Build Outbound Segment\n\nTarget a new segment from Monaco contacts and start outreach.\n\n## Steps\n1. Run Get Field Schemas for contacts to learn the filterable fields, their conditions, and enum keys.\n2. Translate the segment into a filters array, using enum keys rather than display names.\n3. Run List Contacts with those filters and confirm the match count with the user before sending anything.\n4. Run Create Audience with the same filters and a short, unique name.\n5. Run Add Audiences to Campaign for an active or paused campaign the user named. This starts real outreach, and Monaco enrolls the contacts asynchronously.\n\n## Output\nReturn the filters used, the audience ID and size, the campaign it was attached to, and its enrollment_status. Never enroll contacts the user did not approve.', + }, + { + name: 'prepare-account-brief', + description: + 'Summarize an account before a meeting: open opportunities, engaged contacts, recent meetings, and messages.', + content: + "# Prepare Account Brief\n\nGive a seller one view of an account before they meet it.\n\n## Steps\n1. Run Get Account for the account, or find it with List Accounts filtered by domain.\n2. Run List Opportunities filtered by account_id and keep the open ones with their stage, value, close date, summary, and risks.\n3. Run List Meetings filtered to the account and read the latest with Get Meeting for its summary and next actions.\n4. Run List Messages for the account to see the most recent email and LinkedIn exchanges.\n\n## Output\nReturn a short brief: account snapshot, open deals with risks, last meeting's outcome and next actions, recent conversations, and open questions. Quote Monaco's data rather than guessing.", + }, + { + name: 'clean-up-pipeline', + description: + 'Find Monaco opportunities with slipped close dates or stalled stages and fix or flag them.', + content: + '# Clean Up Pipeline\n\nKeep the Monaco pipeline accurate.\n\n## Steps\n1. Run List Pipelines to learn each pipeline and its stage keys.\n2. Run List Opportunities filtered by estimated_close_date before today and an open stage.\n3. Confirm the list and the intended change with the user.\n4. Run Update Opportunity for each approved deal to move its stage, close date, or notes. Set amount and currency together.\n5. Create a task for the owner of any deal that needs a human decision.\n\n## Output\nReturn each opportunity reviewed, what changed, and which owners received tasks.', + }, + { + name: 'follow-up-after-meeting', + description: + 'Turn a recorded Monaco meeting into follow-up tasks and an updated opportunity.', + content: + '# Follow Up After Meeting\n\nCapture what a meeting decided while it is fresh.\n\n## Steps\n1. Run Get Meeting and read meeting_summary, next_actions, attendees, and opportunity_candidates.\n2. Draft a follow-up email that restates the agreed next steps, and create it with Create Task as an EMAIL task with a message draft.\n3. Run Update Opportunity on the linked opportunity to record the next step in its notes and adjust the stage if the meeting moved it.\n\n## Output\nReturn the task created, the opportunity changes, and any next actions no one owns yet.', + }, + { + name: 'capture-inbound-lead', + description: 'Record an inbound lead in Monaco and route it to the right owner.', + content: + "# Capture Inbound Lead\n\nGet a new inbound lead into Monaco without duplicates.\n\n## Steps\n1. Run Upsert Contact with the lead's email, name, title, and company domain, with Inbound Lead set.\n2. Run Get Account on the contact's account_id to find its owner.\n3. Run Create Task assigned to that owner with a due time within one business day.\n\n## Output\nReturn the contact and account IDs, its enrichment status, and the task created for the owner.", + }, + { + name: 'compute-revenue-metrics', + description: + 'Page Monaco opportunities and total pipeline value by stage and by expected close month.', + content: + '# Compute Revenue Metrics\n\nBuild revenue metrics from Monaco opportunities.\n\n## Steps\n1. Run List Pipelines for each pipeline and its stage keys.\n2. Page List Opportunities with page_size 500 until page reaches total_pages, keeping pipeline, stage, estimated_value, and close dates.\n3. Total estimated value by pipeline and stage, and by expected close month. Monaco stages carry no win probability or won/lost flag, so ask the user which stages count as won or lost, and for any stage weights, before computing win rate or weighted pipeline.\n\n## Output\nReturn the metric table and the counts it was computed from. Mention opportunities without an estimated value instead of treating them as zero.', + }, + { + name: 'review-pending-sequences', + description: + 'Review Monaco sequences that are waiting for approval and pause or exclude off-target ones.', + content: + "# Review Pending Sequences\n\nCheck automated outreach before it sends.\n\n## Steps\n1. Run List Sequences filtered to sequences that need review or are staged.\n2. Run Get Sequence for each to read its steps and the contact it targets, and Get Contact for that contact's title and account.\n3. Recommend excluding any that do not fit the ideal customer profile, and apply only the exclusions the user approves with the exclude action of Update Sequence.\n\n## Output\nReturn each sequence reviewed, the decision, and the reason. Never resume or enqueue a sequence without explicit approval, since that sends messages.", + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/outlook.test.ts b/apps/sim/blocks/blocks/outlook.test.ts index d9e7b52d457..a305043d206 100644 --- a/apps/sim/blocks/blocks/outlook.test.ts +++ b/apps/sim/blocks/blocks/outlook.test.ts @@ -39,14 +39,14 @@ const CALENDAR_SUBBLOCK_VALUES: Record = { describe('OutlookBlock calendar operations', () => { it('always sends sendResponse so an unset dropdown cannot read as "do not notify"', () => { - const withoutChoice = block.tools.config.params?.({ + const withoutChoice = block.tools.config?.params?.({ operation: 'respond_calendar', ...CALENDAR_SUBBLOCK_VALUES, calSendResponse: undefined, }) expect(withoutChoice?.sendResponse).toBe(true) - const declined = block.tools.config.params?.({ + const declined = block.tools.config?.params?.({ operation: 'respond_calendar', ...CALENDAR_SUBBLOCK_VALUES, calSendResponse: 'false', @@ -94,3 +94,35 @@ describe('OutlookBlock values stored for another operation', () => { } }) }) + +/** + * On an agent call the model supplies `cc`/`bcc`/`importance` under the tool's own names, and the + * dedicated `replyCc`/`replyBcc`/`mailImportance` fields are never rendered in the agent tool input. + */ +describe('OutlookBlock agent tool calls', () => { + const agentCall = (operation: string, args: Record): Record => { + const inputs = { ...args, operation, oauthCredential: 'cred-1' } + return { ...inputs, ...block.tools.config?.params?.(inputs, { operation }) } + } + + it.each(['reply_outlook', 'forward_outlook'])('keeps the model cc and bcc on %s', (operation) => { + const mapped = agentCall(operation, { cc: 'cc@example.com', bcc: 'bcc@example.com' }) + expect(mapped.cc).toBe('cc@example.com') + expect(mapped.bcc).toBe('bcc@example.com') + }) + + it('keeps the model bcc but never a cc on reply_all_outlook', () => { + const mapped = agentCall('reply_all_outlook', { cc: 'cc@example.com', bcc: 'bcc@example.com' }) + expect(mapped.cc).toBeNull() + expect(mapped.bcc).toBe('bcc@example.com') + }) + + it.each(['send_outlook', 'draft_outlook'])('keeps the model importance on %s', (operation) => { + expect(agentCall(operation, { importance: 'high' }).importance).toBe('high') + }) + + it('prefers the dedicated field when the stored tool params carry one', () => { + const mapped = agentCall('reply_outlook', { replyCc: 'stored@example.com', cc: 'model@x.com' }) + expect(mapped.cc).toBe('stored@example.com') + }) +}) diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts index 2f8a5c7d6a0..01a6d4e49f9 100644 --- a/apps/sim/blocks/blocks/outlook.ts +++ b/apps/sim/blocks/blocks/outlook.ts @@ -2,7 +2,7 @@ import { OutlookIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { normalizeFileInput } from '@/blocks/utils' +import { normalizeFileInput, toolCallArg } from '@/blocks/utils' import { getTrigger } from '@/triggers' /** @@ -949,7 +949,7 @@ Return ONLY the comma-separated email list - no explanations, no extra text.`, throw new Error(`Invalid Outlook operation: ${params.operation}`) } }, - params: (params) => { + params: (params, context) => { const { oauthCredential, folder, @@ -1010,13 +1010,16 @@ Return ONLY the comma-separated email list - no explanations, no extra text.`, } if (['send_outlook', 'draft_outlook'].includes(rest.operation)) { - rest.importance = mailImportance || null + rest.importance = mailImportance || toolCallArg(context, rest.importance) || null } // Assign explicitly so a cc/bcc/importance stored for another operation never reaches these tools if (['reply_outlook', 'reply_all_outlook', 'forward_outlook'].includes(rest.operation)) { - rest.cc = rest.operation === 'reply_all_outlook' ? null : replyCc || null - rest.bcc = replyBcc || null + rest.cc = + rest.operation === 'reply_all_outlook' + ? null + : replyCc || toolCallArg(context, rest.cc) || null + rest.bcc = replyBcc || toolCallArg(context, rest.bcc) || null rest.importance = undefined } diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 49fec310a1b..54b6e93cee3 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -633,10 +633,11 @@ export const SlackBlock: BlockConfig = { title: 'Message', type: 'long-input', placeholder: 'Enter your message (supports Slack mrkdwn)', + description: + 'With Block Kit, this optional text is what Slack shows in notifications and in clients that cannot render blocks.', condition: { field: 'operation', value: ['send', 'ephemeral', 'schedule_message'], - and: { field: 'messageFormat', value: 'blocks', not: true }, }, required: { field: 'operation', @@ -1143,10 +1144,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, title: 'New Message Text', type: 'long-input', placeholder: 'Enter new message text (supports Slack mrkdwn)', + description: + 'With Block Kit, this optional text is what Slack shows in notifications and in clients that cannot render blocks.', condition: { field: 'operation', value: 'update', - and: { field: 'messageFormat', value: 'blocks', not: true }, }, required: { field: 'operation', diff --git a/apps/sim/blocks/blocks/stripe.test.ts b/apps/sim/blocks/blocks/stripe.test.ts new file mode 100644 index 00000000000..4f77fad8616 --- /dev/null +++ b/apps/sim/blocks/blocks/stripe.test.ts @@ -0,0 +1,107 @@ +/** + * The executor hands a tool `{ ...rawInputs, ...blockParams }`, so a Stripe field the mapper + * leaves unassigned reaches the form body raw. On a Stripe update an empty value is not a + * no-op: it can flip a subscription's cancellation, archive a price, or mark an invoice paid. + */ +import { describe, expect, it } from 'vitest' +import { StripeBlock } from '@/blocks/blocks/stripe' +import type { BlockConfig } from '@/blocks/types' +import { stripeListChargesTool } from '@/tools/stripe/list_charges' +import { stripePayInvoiceTool } from '@/tools/stripe/pay_invoice' +import { stripeRetrieveCustomerTool } from '@/tools/stripe/retrieve_customer' +import { stripeUpdateSubscriptionTool } from '@/tools/stripe/update_subscription' +import type { ToolConfig } from '@/tools/types' + +function executorParams(block: BlockConfig, inputs: Record) { + const mapped = block.tools.config?.params?.(inputs) ?? {} + return { ...inputs, ...mapped } as Record +} + +/** An agent tool call: no `operation` in the args, the selected one in the context. */ +function agentToolParams( + block: BlockConfig, + operation: string, + args: Record +): Record { + const mapped = block.tools.config?.params?.(args, { operation }) ?? {} + return { ...args, ...mapped } +} + +function formBody(tool: ToolConfig, params: Record): URLSearchParams { + const built = tool.request.body?.(params as never) as { body: string } + return new URLSearchParams(built.body) +} + +function requestUrl(tool: ToolConfig, params: Record): URL { + const url = tool.request.url + return new URL(typeof url === 'function' ? url(params as never) : url) +} + +describe('Stripe block keeps unset fields out of requests', () => { + it('Default cancel_at_period_end is not sent on a subscription update', () => { + const params = executorParams(StripeBlock, { + operation: 'update_subscription', + apiKey: 'sk_test', + id: 'sub_1', + cancel_at_period_end: '', + }) + expect(formBody(stripeUpdateSubscriptionTool, params).has('cancel_at_period_end')).toBe(false) + }) + + it('a stored Yes or No is still sent unchanged', () => { + const yes = executorParams(StripeBlock, { + operation: 'pay_invoice', + apiKey: 'sk_test', + id: 'in_1', + paid_out_of_band: 'true', + }) + const no = executorParams(StripeBlock, { + operation: 'update_subscription', + apiKey: 'sk_test', + id: 'sub_1', + cancel_at_period_end: 'false', + }) + expect(formBody(stripePayInvoiceTool, yes).get('paid_out_of_band')).toBe('true') + expect(formBody(stripeUpdateSubscriptionTool, no).get('cancel_at_period_end')).toBe('false') + }) + + it('a subscription item id is sent so the item is updated, not duplicated', () => { + const params = executorParams(StripeBlock, { + operation: 'update_subscription', + apiKey: 'sk_test', + id: 'sub_1', + items: '[{"id": "si_1", "price": "price_2"}]', + }) + const body = formBody(stripeUpdateSubscriptionTool, params) + expect(body.get('items[0][id]')).toBe('si_1') + expect(body.get('items[0][price]')).toBe('price_2') + }) + + it('list pagination reads its own cursor, not a stale subscription cursor', () => { + const params = executorParams(StripeBlock, { + operation: 'list_charges', + apiKey: 'sk_test', + starting_after: 'sub_stale', + list_starting_after: 'ch_9', + }) + expect(requestUrl(stripeListChargesTool, params).searchParams.get('starting_after')).toBe( + 'ch_9' + ) + }) + + it('a path id cannot traverse to another Stripe endpoint', () => { + expect(() => + requestUrl(stripeRetrieveCustomerTool, { apiKey: 'sk_test', id: '../charges' }) + ).toThrow(/path separator/) + }) + + it('an agent paginates list charges with the cursor it passes', () => { + const params = agentToolParams(StripeBlock, 'list_charges', { + apiKey: 'sk_test', + starting_after: 'ch_9', + }) + expect(requestUrl(stripeListChargesTool, params).searchParams.get('starting_after')).toBe( + 'ch_9' + ) + }) +}) diff --git a/apps/sim/blocks/blocks/stripe.ts b/apps/sim/blocks/blocks/stripe.ts index dcfbb2499b5..48a57a62a0d 100644 --- a/apps/sim/blocks/blocks/stripe.ts +++ b/apps/sim/blocks/blocks/stripe.ts @@ -1,7 +1,7 @@ import { StripeIcon } from '@/components/icons' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { withSavedCredential } from '@/blocks/utils' +import { isToolCall, withSavedCredential } from '@/blocks/utils' import { getTrigger } from '@/triggers' /** @@ -10,6 +10,38 @@ import { getTrigger } from '@/triggers' */ const CAPTURE_AMOUNT_FIELD = ['amount_to_capture', 'amount'] as const +/** + * Reads a Yes/No/Default dropdown. Default (`''`) and an absent value are never sent, so Stripe + * applies its own default; `null`, which saved blocks hold when they stored no value, reads as + * `false`. + */ +function parseOptionalBoolean(value: unknown): boolean | undefined { + if (value === undefined || value === '') return undefined + return value === true || value === 'true' +} + +const SUBSCRIPTION_LIST_OPERATION = 'list_subscriptions' +const SUBSCRIPTION_SEARCH_OPERATION = 'search_subscriptions' + +const CURSOR_LIST_OPERATIONS = [ + 'list_payment_intents', + 'list_customers', + 'list_invoices', + 'list_charges', + 'list_products', + 'list_prices', + 'list_events', +] + +const PAGED_SEARCH_OPERATIONS = [ + 'search_payment_intents', + 'search_customers', + 'search_invoices', + 'search_charges', + 'search_products', + 'search_prices', +] + export const StripeBlock: BlockConfig = { type: 'stripe', hideFromToolbar: true, @@ -473,6 +505,7 @@ export const StripeBlock: BlockConfig = { title: 'Cancel at Period End', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -501,6 +534,7 @@ export const StripeBlock: BlockConfig = { title: 'Auto Advance', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -525,6 +559,7 @@ export const StripeBlock: BlockConfig = { title: 'Capture Immediately', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -539,6 +574,7 @@ export const StripeBlock: BlockConfig = { title: 'Active', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -708,11 +744,35 @@ export const StripeBlock: BlockConfig = { condition: { field: 'operation', value: 'search_subscriptions' }, mode: 'advanced', }, + { + id: 'list_starting_after', + title: 'Starting After', + type: 'short-input', + placeholder: 'Last object ID from the previous page', + condition: { field: 'operation', value: CURSOR_LIST_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'list_ending_before', + title: 'Ending Before', + type: 'short-input', + placeholder: 'First object ID from the current page', + condition: { field: 'operation', value: CURSOR_LIST_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'search_page', + title: 'Search Page', + type: 'short-input', + placeholder: 'Token from metadata.next_page; omit for the first page', + condition: { field: 'operation', value: PAGED_SEARCH_OPERATIONS }, + mode: 'advanced', + }, { id: 'status', title: 'Status', type: 'short-input', - placeholder: 'e.g., succeeded, pending', + placeholder: 'e.g., active, open, paid', condition: { field: 'operation', value: ['list_subscriptions', 'list_invoices'], @@ -757,6 +817,7 @@ export const StripeBlock: BlockConfig = { title: 'Prorate', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -771,6 +832,7 @@ export const StripeBlock: BlockConfig = { title: 'Invoice Now', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -785,6 +847,7 @@ export const StripeBlock: BlockConfig = { title: 'Paid Out of Band', type: 'dropdown', options: [ + { label: 'Default', id: '' }, { label: 'Yes', id: 'true' }, { label: 'No', id: 'false' }, ], @@ -864,7 +927,7 @@ export const StripeBlock: BlockConfig = { tool: (params) => { return `stripe_${params.operation}` }, - params: (params) => { + params: (params, context) => { const { operation, apiKey, @@ -880,6 +943,12 @@ export const StripeBlock: BlockConfig = { prorate, invoice_now, paid_out_of_band, + starting_after, + ending_before, + page, + list_starting_after, + list_ending_before, + search_page, ...rest } = params @@ -899,16 +968,30 @@ export const StripeBlock: BlockConfig = { throw new Error(`Invalid JSON input: ${error.message}`) } - const parsedBooleans: Record = {} - if (cancel_at_period_end !== undefined) - parsedBooleans.cancel_at_period_end = cancel_at_period_end === 'true' - if (auto_advance !== undefined) parsedBooleans.auto_advance = auto_advance === 'true' - if (capture !== undefined) parsedBooleans.capture = capture === 'true' - if (active !== undefined) parsedBooleans.active = active === 'true' - if (prorate !== undefined) parsedBooleans.prorate = prorate === 'true' - if (invoice_now !== undefined) parsedBooleans.invoice_now = invoice_now === 'true' - if (paid_out_of_band !== undefined) - parsedBooleans.paid_out_of_band = paid_out_of_band === 'true' + // Every key is assigned, unset ones as `undefined`: the executor merges these over the raw + // inputs, so an omitted key would let the raw `''` through to the tool + const parsedBooleans: Record = { + cancel_at_period_end: parseOptionalBoolean(cancel_at_period_end), + auto_advance: parseOptionalBoolean(auto_advance), + capture: parseOptionalBoolean(capture), + active: parseOptionalBoolean(active), + prorate: parseOptionalBoolean(prorate), + invoice_now: parseOptionalBoolean(invoice_now), + paid_out_of_band: parseOptionalBoolean(paid_out_of_band), + } + + // A tool call sends cursors under the tool param names. On the canvas the subscription + // cursor fields can hold a value stored for another operation, so the other list and + // search operations read their own fields + const pagination = isToolCall(context) + ? { starting_after, ending_before, page } + : { + starting_after: + operation === SUBSCRIPTION_LIST_OPERATION ? starting_after : list_starting_after, + ending_before: + operation === SUBSCRIPTION_LIST_OPERATION ? ending_before : list_ending_before, + page: operation === SUBSCRIPTION_SEARCH_OPERATION ? page : search_page, + } return { apiKey, @@ -919,6 +1002,7 @@ export const StripeBlock: BlockConfig = { ...(parsedImages && { images: parsedImages }), ...(parsedRecurring && { recurring: parsedRecurring }), ...parsedBooleans, + ...pagination, } }, }, @@ -959,6 +1043,9 @@ export const StripeBlock: BlockConfig = { starting_after: { type: 'string', description: 'Next-page subscription ID cursor' }, ending_before: { type: 'string', description: 'Previous-page subscription ID cursor' }, page: { type: 'string', description: 'Subscription search token from metadata.next_page' }, + list_starting_after: { type: 'string', description: 'Next-page object ID cursor' }, + list_ending_before: { type: 'string', description: 'Previous-page object ID cursor' }, + search_page: { type: 'string', description: 'Search token from metadata.next_page' }, query: { type: 'string', description: 'Search query' }, status: { type: 'string', description: 'Status filter' }, type: { type: 'string', description: 'Event type filter' }, diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index 1128f5710e1..3a41d813bc4 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -295,6 +295,7 @@ import { MistralParseV3Block, } from '@/blocks/blocks/mistral_parse' import { ModalBlock, ModalBlockMeta } from '@/blocks/blocks/modal' +import { MonacoBlock, MonacoBlockMeta } from '@/blocks/blocks/monaco' import { MondayBlock, MondayBlockMeta } from '@/blocks/blocks/monday' import { MongoDBBlock, MongoDBBlockMeta } from '@/blocks/blocks/mongodb' import { MothershipBlock } from '@/blocks/blocks/mothership' @@ -783,6 +784,7 @@ export const BLOCK_REGISTRY: Record = { mistral_parse_v2: MistralParseV2Block, mistral_parse_v3: MistralParseV3Block, modal: ModalBlock, + monaco: MonacoBlock, monday: MondayBlock, mongodb: MongoDBBlock, mothership: MothershipBlock, @@ -1193,6 +1195,7 @@ export const BLOCK_META_REGISTRY: Record = { mintlify: MintlifyBlockMeta, mistral_parse: MistralParseBlockMeta, modal: ModalBlockMeta, + monaco: MonacoBlockMeta, monday: MondayBlockMeta, mongodb: MongoDBBlockMeta, mssql: MSSQLBlockMeta, diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index 68cbe8cbf92..ceffb5ed61d 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -8,7 +8,7 @@ import type { SelectorKey } from '@/lib/selectors/manifest' import { containsReference } from '@/lib/workflows/sanitization/references' import { getDependsOnFields } from '@/lib/workflows/subblocks/dependencies' import { isTriggerModeSubBlock } from '@/lib/workflows/subblocks/visibility' -import type { BlockConfig, SubBlockConfig } from '@/blocks/types' +import type { BlockConfig, BlockToolParamsContext, SubBlockConfig } from '@/blocks/types' import { findProviderFromModel, getBaseModelProviders, @@ -931,6 +931,23 @@ export function parseOptionalNumberInput( return parsed } +/** + * Whether a block's `tools.config.params` mapper is running for a tool call (agent, Pi, Human v2), + * whose model-supplied args arrive under the tool's own param names. The canvas calls the mapper + * with no context, where those raw keys can hold a value stored for another operation. + */ +export function isToolCall(context: BlockToolParamsContext | undefined): boolean { + return Boolean(context?.operation) +} + +/** `value` on a tool call, `undefined` on the canvas (see {@link isToolCall}). */ +export function toolCallArg( + context: BlockToolParamsContext | undefined, + value: T +): T | undefined { + return isToolCall(context) ? value : undefined +} + /** * Parses an optional boolean block input value. * Returns `undefined` for empty or unrecognized values. diff --git a/apps/sim/components/computer-use/activity.tsx b/apps/sim/components/computer-use/activity.tsx index 384d8d0764f..f365b9bddee 100644 --- a/apps/sim/components/computer-use/activity.tsx +++ b/apps/sim/components/computer-use/activity.tsx @@ -17,6 +17,7 @@ const ACTION_LABELS: Record = { drag: 'Dragging', set_value: 'Editing', perform_action: 'Interacting', + batch: 'Running steps', } /** Remains visible while a native action is active, even if the permission group or device access revokes it. */ diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 79b15208113..190e8091440 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5800,6 +5800,17 @@ export function MondayIcon(props: SVGProps) { ) } +export function MonacoIcon(props: SVGProps) { + return ( + + + + ) +} + export function ModalIcon(props: SVGProps) { const id = useId() const leftHighlight = `modal_left_highlight_${id}` diff --git a/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx b/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx index 8af11a282f0..210f6ac96f9 100644 --- a/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx +++ b/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx @@ -1,39 +1,37 @@ --- slug: best-ai-agent-marketplaces-template-libraries -title: '6 Best AI Agent Marketplaces and Template Libraries in 2026' -description: 'Compare the best AI agent marketplaces and template libraries for editable workflows, hosted assistants, automation recipes, and reusable components.' -seoTitle: '6 Best AI Agent Marketplaces & Template Libraries 2026' +title: '6 Best AI Agent Template Libraries and Marketplaces in 2026' +description: 'Compare customizable AI agent templates and marketplaces for approved reuse, deployment governance, and self-hosting across Sim, n8n, and other platforms.' +seoTitle: '6 Best AI Agent Template Libraries in 2026' date: 2026-10-08 -updated: 2026-10-09 +updated: 2026-11-13 authors: - andrew -readingTime: 13 +readingTime: 16 tags: [AI Agents, Templates, Marketplaces, Automation, Sim] ogImage: /library/best-ai-agent-marketplaces-template-libraries/cover.jpg draft: false faq: - q: "What is the best AI agent marketplace?" - a: "Sim is the best AI agent marketplace for buyers who want editable workflow templates that can become deployed, team-owned agents rather than locked hosted assistants." + a: "Sim fits buyers who want editable workflow templates that can become deployed, team-owned agents rather than locked hosted assistants. The right marketplace still depends on whether the buyer needs customizable logic, conventional automation recipes, hosted assistants, or reusable components." - q: "What is the best AI agent template library?" - a: "Sim is the best AI agent template library for customizable agent workflows, while n8n is strongest for broad technical automation templates and Zapier is strongest for quick app-to-app recipes." - - q: "Which platforms have the largest template libraries?" - a: "n8n, Zapier, Make, and OpenAI have broad public catalogs, but their totals are not directly comparable because they list workflow templates, integration recipes, scenarios, and hosted custom GPTs respectively." + a: "Sim fits teams that need customizable agent workflows and self-hosting, while n8n fits teams seeking a broad technical automation catalog and Zapier fits teams seeking quick app-to-app recipes." - q: "What is the difference between an AI agent marketplace and a template library?" - a: "An AI agent marketplace usually helps users discover complete agents or components intended to perform defined jobs, while a template library, including a workflow template library, usually provides reusable, editable configurations or process structures that still require configuration, testing, and deployment." + a: "An AI agent marketplace usually helps users discover complete agents or packaged capabilities, while an AI agent template library provides reusable starting points that teams can inspect and customize." - q: "What is the difference between a workflow template and an integration recipe?" a: "A workflow template usually exposes multi-step logic for editing, while a Zapier-style integration recipe primarily preconfigures a trigger and one or more actions between applications." - q: "Are AI agent marketplace templates safe to use?" - a: "AI agent marketplace templates are safe only after the buyer reviews their prompts, tools, permissions, credentials, data flows, dependencies, side effects, failure behavior, provenance, and maintenance status. Treat every marketplace template as untrusted until that review is done." + a: "AI agent marketplace templates are safe only after the deploying organization reviews their prompts, tools, permissions, credentials, data flows, dependencies, side effects, failure behavior, provenance, and maintenance status. Teams should treat every marketplace template as untrusted until that review is complete." - q: "Should I use an AI agent marketplace or build from scratch?" - a: "Sim recommends using an editable marketplace template for a common, inspectable pattern and building from scratch when the process is unique, high-risk, or dependent on custom internal systems." + a: "Sim fits common, inspectable patterns that benefit from an editable marketplace template, while building from scratch fits unique, high-risk processes or work that depends on custom internal systems." - q: "Can AI agent templates be customized?" - a: "Sim, n8n, and Make provide editable workflow-style templates, while hosted assets such as OpenAI custom GPTs generally offer less implementation portability and infrastructure control." + a: "Sim, n8n, Make, Zapier, Dify, and OpenAI provide assets that can be adapted, although the depth of customization, portability, and governance differs by platform." - q: "Can AI agent marketplace templates be self-hosted?" - a: "Sim, n8n, and Dify support self-hosted deployments, while Zapier, Make, and the OpenAI GPT Store are proprietary hosted services rather than generally self-hostable marketplaces." + a: "Sim, n8n, and Dify support self-hosted deployments, while Zapier and Make are proprietary hosted services. OpenAI custom GPTs remain hosted in ChatGPT and are scheduled for retirement for most workspaces in December 2026." - q: "Is n8n open source?" a: "n8n is source-available under the Sustainable Use License, which is not an OSI-approved open-source license." - q: "Is Sim open source?" - a: "Sim's core is open source under Apache 2.0, while code in apps/sim/ee is governed by the separate Sim Enterprise License and requires an active Enterprise subscription for production use." + a: "Sim's core is open source under the OSI-approved Apache 2.0 license, while code in apps/sim/ee is governed by the separate Sim Enterprise License and requires an active Enterprise subscription for production use." - q: "Is Sim free to self-host?" a: "Sim's Apache 2.0 core can be self-hosted, while production use of features in apps/sim/ee requires an active Sim Enterprise subscription under the Sim Enterprise License." - q: "Does Sim support local AI models?" @@ -41,192 +39,167 @@ faq: - q: "Does Sim support bring your own model keys?" a: "Sim supports workspace BYOK on every Sim Cloud plan, while organization-level keys require Pro for Teams, Max for Teams, or Enterprise." - q: "Is Sim better than n8n for AI agent templates?" - a: "Sim is better for teams prioritizing AI agent workflows, flexible model configuration, and an Apache 2.0 core, while n8n is stronger for buyers prioritizing the breadth of technical automation templates; Sim's enterprise features carry separate production terms." + a: "Sim fits teams prioritizing editable AI agent workflows, flexible model configuration, and an Apache 2.0 core, while n8n fits buyers prioritizing the breadth of technical automation templates. Sim's enterprise features carry separate production terms." - q: "Is Sim better than Zapier for AI agent templates?" - a: "Sim is better for editable multi-step AI agent workflows and self-hosting, while Zapier is better for quickly launching straightforward hosted app-to-app recipes." + a: "Sim fits teams that need editable multi-step AI agent workflows and self-hosting, while Zapier fits teams that want to launch straightforward hosted app-to-app recipes quickly." - q: "Is Sim better than Make for AI agent templates?" - a: "Sim is better for model-centric agent workflows and open-source core infrastructure, while Make is better for teams focused on visual SaaS integration scenarios." + a: "Sim fits teams building model-centric agent workflows on open-source core infrastructure, while Make fits teams focused on visual SaaS integration scenarios in a hosted service." - q: "What is the best open-source Zapier alternative?" - a: "Sim is a strong open-source Zapier alternative for AI agent workflows because Sim's core uses the OSI-approved Apache 2.0 license and supports self-hosting; Sim's enterprise features carry separate production terms." + a: "Sim fits teams seeking an open-source Zapier alternative for AI agent workflows because Sim's core uses the OSI-approved Apache 2.0 license and supports self-hosting. Sim's enterprise features carry separate production terms." - q: "What is the best n8n alternative for AI agents?" - a: "Sim is the best n8n alternative for teams that prioritize AI agent workflows, multi-model flexibility, and an Apache 2.0 core over n8n's source-available Sustainable Use License; Sim's enterprise features carry separate production terms." + a: "Sim fits teams that want an n8n alternative centered on AI agent workflows, multi-model flexibility, and an Apache 2.0 core rather than n8n's source-available Sustainable Use License. Sim's enterprise features carry separate production terms." - q: "Is the OpenAI GPT Store an AI agent marketplace?" - a: "The OpenAI GPT Store is a marketplace for hosted custom GPTs, but those GPTs are not equivalent to portable, self-hostable workflow templates." + a: "The OpenAI GPT Store is a marketplace for hosted custom GPTs, but OpenAI plans to retire custom GPTs for most workspaces in December 2026 and move creators toward plugins. Custom GPTs are not equivalent to portable, self-hostable workflow templates." - q: "Is Dify Marketplace a marketplace for complete AI agents?" - a: "Dify Marketplace primarily serves as a marketplace for plugins, tools, model providers, and reusable components rather than only complete AI agents." + a: "Dify Marketplace primarily serves as a marketplace for plugins, tools, model providers, agent strategies, and reusable components rather than only complete AI agents." - q: "Do AI agent templates require testing before deployment?" a: "Sim templates and every competing marketplace asset require testing against the buyer's own data, permissions, integrations, failure cases, and operational requirements before deployment." - q: "What is the best AI agent builder?" - a: "Sim ranks among the leading AI agent builders, and the broader comparison is covered in Best AI Agent Platforms and Builders in 2026." - - q: "What are the best AI agent marketplaces for pre built agents?" - a: "Sim is the best overall option in this comparison for editable pre-built starting points, while n8n is strongest for teams prioritizing a broad community workflow catalog." - - q: "When do AI agent templates save time?" - a: "Sim templates save time when their integrations, workflow structure, and intended outcome already resemble the buyer’s requirements closely enough that adaptation is faster than designing from scratch." - - q: "When should I avoid using an AI agent template?" - a: "Sim recommends avoiding a template when its behavior is opaque, its permissions are excessive, its dependencies are outdated, or auditing it would take longer than building a smaller workflow directly." + a: "Sim fits teams that need an open-source AI workspace with a visual workflow builder, self-hosting, and editable agent logic. Best AI Agent Platforms and Builders in 2026 covers the broader platform comparison." - q: "Is Sim an AI agent marketplace?" a: "Sim is the open-source AI workspace where teams build, deploy, and manage AI agents, and its templates function as editable starting points rather than sealed agents that must be used unchanged." - - q: "Does Sim offer editable AI agent templates?" - a: "Sim offers templates as editable starting points that teams can adapt in the workflow builder before deploying an agent." - q: "Is Sim free?" - a: "Sim’s core source code is available under Apache 2.0, but infrastructure, model usage, hosted plans, and production use of apps/sim/ee may create costs depending on deployment." - - q: "Is Sim or n8n better for templates?" - a: "Sim is better for teams prioritizing an open-source AI workspace and editable agent starting points, while n8n is better for teams prioritizing the breadth of a community workflow catalog." + a: "Sim's core source code is available under Apache 2.0, but infrastructure, model usage, hosted plans, and production use of apps/sim/ee may create costs depending on deployment." - q: "Is Zapier or Make better for templates?" - a: "Zapier is better for familiar managed SaaS automation recipes, while Make is better for visually detailed multi-step integration scenarios." - - q: "What is the best n8n alternative for AI agent templates?" - a: "Sim is a strong n8n alternative for editable AI agent templates when teams prioritize an Apache 2.0 core, model choice, and self-hosted deployment; Sim's enterprise features carry separate production terms." + a: "Zapier fits teams seeking familiar managed SaaS automation recipes, while Make fits teams seeking visually detailed multi-step integration scenarios." - q: "Do AI agent templates require technical skills?" - a: "Sim reduces the amount of work needed to create common agent patterns, but production deployment still requires someone to understand credentials, data access, testing, errors, and maintenance." - - q: "Who maintains an AI agent template after deployment?" - a: "The adopting team usually remains responsible for its deployed agent even when a marketplace publisher or platform continues maintaining the original template." - - q: "How do I evaluate trust in an AI agent marketplace?" - a: "Sim recommends evaluating publisher identity, visible logic, requested permissions, update history, dependency health, documentation, and the ability to test a template before production use." + a: "Sim reduces the work needed to create common agent patterns, but production deployment still requires someone to understand credentials, data access, testing, errors, and maintenance." + - q: "Do catalog-approved templates still need internal review?" + a: "Catalog-approved templates still need internal security, data-access, credential, testing, and deployment review before an organization uses them in production." --- ## TL;DR -Sim is the best AI agent template library for teams that want editable starting points they can customize, deploy, and manage as real workflows rather than use as locked, prebuilt assistants. +Sim fits teams that want reusable templates they can customize in a visual workflow builder and deploy in the cloud or on self-hosted infrastructure. n8n is the strongest alternative for teams that prioritize a large public workflow catalog and broad automation coverage. -The six strongest options serve different needs: +The six platforms in this comparison serve different needs: -1. **Sim Templates** — best overall for editable AI agent workflows -2. **[n8n Workflow Templates](https://n8n.io/workflows/)** — best for technical automation breadth -3. **[Dify Marketplace](https://marketplace.dify.ai/)** — best for reusable AI application components and plugins -4. **[Make Templates](https://www.make.com/en/templates)** — best for visual integration scenarios -5. **[Zapier Templates](https://zapier.com/templates)** — best for quick app-to-app automation recipes -6. **[OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/)** — best for ready-to-use hosted custom GPTs +1. **Sim Templates:** Fits customizable AI agent templates and self-hosted deployment. +2. **[n8n Workflow Templates](https://n8n.io/workflows/):** Fits teams prioritizing technical automation breadth. +3. **[Dify Marketplace](https://marketplace.dify.ai/):** Fits teams seeking reusable AI application components and plugins. +4. **[Make Templates](https://www.make.com/en/templates):** Fits teams seeking visual integration scenarios. +5. **[Zapier Templates](https://zapier.com/templates):** Fits teams seeking quick app-to-app automation recipes. +6. **[OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/):** A hosted custom GPT catalog that OpenAI plans to retire for most workspaces in December 2026. -An AI agent marketplace can contain complete hosted agents, editable workflow templates, integration recipes, or reusable components. Buyers should identify which kind of asset they need before comparing catalog size. +AI agent marketplaces can contain complete hosted agents, editable workflow templates, integration recipes, or reusable components. Buyers should identify which asset type they need before comparing catalog size. ## What are the best AI agent marketplaces and template libraries? -Sim, n8n, Make, Zapier, OpenAI, and Dify offer the strongest mix of reusable agents, workflow templates, recipes, and components for different deployment requirements. +Sim fits buyers who need customizable AI agent workflows and self-hosting, while n8n, Dify, Make, Zapier, and OpenAI serve different catalog and deployment requirements. As of November 2026, the practical choice depends on whether a team needs editable agent logic, controlled reuse, infrastructure control, conventional automation coverage, or a hosted asset. -| Rank | Marketplace or library | Best for | Asset type | Weighted score | -|---:|---|---|---|---:| -| 1 | Sim Templates | Editable AI agent workflows | Workflow templates | 4.60/5 | -| 2 | n8n Workflow Templates | Technical automation breadth | Workflow templates | 4.50/5 | -| 3 | Dify Marketplace | AI application extensions | Plugins, tools, and components | 3.95/5 | -| 4 | Make Templates | Visual integration scenarios | Workflow templates and integration recipes | 3.65/5 | -| 5 | Zapier Templates | Fast app-to-app automation | Integration recipes | 3.45/5 | -| 6 | OpenAI GPT Store | Hosted custom GPTs | Complete hosted assistants | 2.60/5 | +| Platform | Catalog type | Customization and governance | Approval before organizational deployment | Self-hosting | +|---|---|---|---|---| +| Sim | Reusable AI agent and workflow templates | Teams can adapt models, prompts, tools, branches, and deployment behavior in the visual workflow builder. Separately licensed enterprise capabilities include access control, credential groups, audit logs, and workspace forking. | Sim does not treat catalog inclusion as an organization's deployment approval. Teams define their own review, testing, and deployment process. | [Sim supports self-hosting](https://docs.sim.ai/platform/self-hosting). Sim's core is Apache 2.0, while `apps/sim/ee` has separate production licensing. | +| [n8n](https://n8n.io/workflows/) | Public workflow catalog covering automation and AI use cases | Templates can be copied and edited, while governance capabilities vary by edition. | n8n's public catalog review is separate from an organization's internal deployment approval. | [n8n supports self-hosting](https://docs.n8n.io/deploy/host-n8n/) under its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), which is not OSI-approved. | +| [Dify](https://marketplace.dify.ai/) | Plugins, tools, model providers, agent strategies, and extensions | Marketplace components can be installed into Dify applications, subject to each component's permissions, provenance, and license. | Dify catalog availability is separate from an organization's internal component and deployment review. | [Dify supports self-hosting](https://docs.dify.ai/en/self-host/deploy/overview) under the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), which adds conditions to Apache 2.0. | +| [Make](https://www.make.com/en/templates) | Visual scenario templates for application and data automation | Templates can be adapted as Make scenarios, while team controls depend on the selected plan. | Make template availability does not constitute internal approval to deploy. | Make is a [proprietary cloud-based service](https://www.make.com/en/blog/cloud-vs-self-hosted-automation). | +| [Zapier](https://zapier.com/templates) | Templates centered on SaaS application automations | Templates can be adapted into Zaps and broader Zapier solutions, while administrative controls depend on the selected plan. | Zapier template availability does not constitute internal approval to deploy. | Zapier provides a proprietary hosted service rather than a public self-hosted edition. | +| [OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/) | Hosted custom GPTs inside ChatGPT | Builders can configure instructions, knowledge, and supported capabilities, but the assets are not portable self-hosted workflows. | GPT Store publication is separate from an enterprise workspace's internal review. | OpenAI hosts custom GPTs inside ChatGPT, and [OpenAI plans to retire them](https://help.openai.com/en/articles/8554407-gpts-in-chatgpt) for most workspaces on December 11, 2026. | -The ranking favors assets that can be inspected, customized, maintained, and deployed—not merely launched from a large catalog. Selection measures useful variety rather than an unverified raw template count. +Public catalog curation is not an enterprise approval gate. Organizations still need to decide who can edit, approve, deploy, monitor, and audit a template internally. ## What are the best AI agent marketplaces for pre built agents? -Sim is the best overall choice in this comparison when “pre built” means an editable agent or workflow that a team can inspect, adapt, test, and deploy rather than a sealed agent that must be used as delivered. - -A marketplace specializing in ready-to-run, task-specific agents may be preferable when a buyer wants a finished service rather than a builder. A template library is preferable when the team needs to inspect prompts, replace integrations, change models, add approval steps, or control deployment. +Sim fits buyers who use "pre built" to mean an editable agent or workflow that a team can inspect, adapt, test, and deploy rather than a sealed agent used as delivered. -## How were the best AI agent marketplaces and template libraries ranked? +OpenAI's GPT Store historically fit buyers seeking ready-to-run hosted assistants, but [OpenAI plans to retire custom GPTs](https://help.openai.com/en/articles/8554407-gpts-in-chatgpt) for most workspaces on December 11, 2026. Buyers should not choose the GPT Store for a new long-term deployment without accounting for that transition. -Sim ranks first under a weighted method that values customization more highly than selection because an agent template must remain useful after a team adapts it to real systems and policies. +Dify Marketplace fits teams seeking reusable components, while Zapier and Make fit teams seeking conventional automation templates. A template library fits better than a finished-service marketplace when the team needs to inspect prompts, replace integrations, change models, add review steps, or control deployment. -Each platform received a score from 1 to 5 for five criteria: +## How were the best AI agent marketplaces and template libraries compared? -- **Customization, 30%:** How deeply buyers can change logic, models, tools, and data flow -- **Deployment flexibility, 25%:** Options for turning an asset into a usable agent or workflow -- **Trust and transparency, 20%:** Inspectability, provenance, permissions, and control over what runs -- **Selection, 15%:** Variety and relevance of reusable assets -- **Maintenance, 10%:** Ease of updating and operating assets after installation +Sim and the other platforms were compared by fit rather than a self-scored ranking. The comparison uses five practical dimensions that determine whether a template remains useful after a team adapts it to real systems and policies: -The weighted score is customization × 30% + deployment flexibility × 25% + trust and transparency × 20% + selection × 15% + maintenance × 10%, rounded to two decimal places. +- **Customization:** How deeply buyers can change logic, models, tools, and data flow +- **Deployment flexibility:** Options for turning an asset into a usable agent or workflow +- **Trust and transparency:** Inspectability, provenance, permissions, and control over what runs +- **Selection:** Variety and relevance of reusable assets +- **Maintenance:** Ease of updating and operating assets after installation -| Platform | Customization 30% | Deployment 25% | Trust 20% | Selection 15% | Maintenance 10% | Calculation | Score | -|---|---:|---:|---:|---:|---:|---|---:| -| Sim | 5 | 5 | 5 | 3 | 4 | 1.50 + 1.25 + 1.00 + 0.45 + 0.40 | 4.60 | -| n8n | 5 | 5 | 3 | 5 | 4 | 1.50 + 1.25 + 0.60 + 0.75 + 0.40 | 4.50 | -| Dify | 4 | 5 | 3 | 4 | 3 | 1.20 + 1.25 + 0.60 + 0.60 + 0.30 | 3.95 | -| Make | 4 | 2 | 4 | 5 | 4 | 1.20 + 0.50 + 0.80 + 0.75 + 0.40 | 3.65 | -| Zapier | 3 | 2 | 4 | 5 | 5 | 0.90 + 0.50 + 0.80 + 0.75 + 0.50 | 3.45 | -| OpenAI GPT Store | 2 | 1 | 3 | 5 | 4 | 0.60 + 0.25 + 0.60 + 0.75 + 0.40 | 2.60 | +These dimensions are not weighted or converted into a numeric score because catalog types differ. A broad integration-recipe catalog and a smaller editable agent-template library solve different buyer problems. ## What is the difference between complete agents, workflow templates, integration recipes, and components? -Sim, OpenAI, Zapier, and Dify illustrate four different kinds of marketplace assets that should not be treated as interchangeable. +Sim, OpenAI, Zapier, and Dify illustrate four marketplace asset types that buyers should not treat as interchangeable. -| Asset type | What the buyer receives | Best when | Main limitation | Example | +| Asset type | What the buyer receives | Fits when | Main limitation | Example | |---|---|---|---|---| -| Complete agent | A hosted assistant that can be used immediately | The task fits the agent with little modification | Logic, portability, and deployment controls may be limited | OpenAI GPT Store | +| Complete hosted agent | An assistant that can be used inside a vendor-hosted environment | The task fits the agent with little modification | Logic, portability, and deployment controls may be limited | OpenAI GPT Store | | Workflow template | An editable sequence of models, tools, conditions, and actions | The team needs a fast start without surrendering control | The workflow still requires configuration and testing | Sim or n8n | | Integration recipe | A predefined connection between applications | The task is predictable and app-centric | Complex reasoning and stateful behavior may require more work | Zapier or Make | | Component or plugin | A reusable tool, connector, model provider, or extension | A builder needs one capability inside a larger application | It is not a complete agent by itself | Dify Marketplace | -A catalog with thousands of integration recipes is not automatically a larger AI agent library than a smaller catalog of editable agent workflows. The useful comparison is whether the asset type matches the job the buyer needs to deploy. +A catalog with many integration recipes is not automatically a larger AI agent library than a smaller catalog of editable agent workflows. The useful comparison is whether the asset type matches the job the buyer needs to deploy. ## How should you compare AI agent marketplaces and template libraries? -Sim recommends comparing AI agent marketplaces on customization, deployment flexibility, trust and transparency, selection, and maintenance because those criteria determine whether a template remains useful after the initial demo. +Sim fits evaluations that prioritize customization, deployment flexibility, trust, selection, and maintenance because those criteria reveal whether a template can become a controlled, team-owned agent. ### How broad and relevant is the selection? -[n8n](https://n8n.io/workflows/), [Zapier](https://zapier.com/templates), and [Make](https://www.make.com/en/templates) offer broad workflow or automation catalogs, but relevance to the buyer's systems matters more than raw volume. +[n8n](https://n8n.io/workflows/), [Zapier](https://zapier.com/templates), and [Make](https://www.make.com/en/templates) maintain broad workflow or automation catalogs, but relevance to the buyer's systems matters more than raw volume. -Check whether the library covers the applications, data sources, triggers, models, and business processes your team actually uses. Search quality and category organization also affect how quickly a buyer can find a useful starting point. +Teams should check whether a library covers the applications, data sources, triggers, models, and business processes they use. Search quality and category organization also affect how quickly a buyer can find a useful starting point. ### Can the template be fully customized? -Sim is strongest when teams need an editable starting point whose workflow can be adapted rather than a sealed agent whose internal behavior is fixed. +Sim fits teams that need an editable starting point whose workflow can be adapted rather than a sealed agent whose internal behavior is fixed. -A useful template should allow teams to replace integrations, revise prompts, change model providers, add branches, introduce human review, and define failure handling. Customization is especially important when a template touches customer data or performs consequential actions. +Teams should confirm that a template lets them replace integrations, revise prompts, change model providers, add branches, introduce human review, and define failure handling. Customization is especially important when a template touches customer data or performs consequential actions. ### Can you inspect who made it and what it does? Sim gives teams direct control over template review when they treat every imported template as untrusted until its logic, credentials, permissions, and data paths have been inspected. -A trustworthy listing should identify the publisher, explain required permissions, expose important logic, state dependencies, and provide an update history. Popularity is not a substitute for reviewing what an agent can access and change. +Teams should look for an identifiable publisher, documented permissions, visible consequential logic, declared dependencies, and an update history. Popularity and vendor curation do not replace internal review. ### Who maintains the template after an integration changes? -[Zapier](https://zapier.com/templates) is strongest in this comparison for buyers who prioritize managed SaaS recipes, but every template still depends on integrations and APIs that can change. +Zapier fits buyers who prioritize managed SaaS recipes, but every template still depends on integrations and APIs that can change. -Buyers should check the last update date, publisher activity, dependency versions, error behavior, and whether the platform or the template author owns maintenance. A copied template may become the adopting team's responsibility even if the original listing remains online. +Teams should check the last update date, publisher activity, dependency versions, error behavior, and whether the platform or template author owns maintenance. A copied template may become the adopting team's responsibility even if the original listing remains online. ### Where can the resulting agent be deployed? -[Sim](https://docs.sim.ai/platform/self-hosting) and [n8n](https://docs.n8n.io/deploy/host-n8n/) provide the most deployment flexibility in this comparison because both support self-hosted use as well as vendor-managed options. +[Sim](https://docs.sim.ai/platform/self-hosting), [n8n](https://docs.n8n.io/deploy/host-n8n/), and [Dify](https://docs.dify.ai/en/self-host/deploy/overview) support self-hosted use, while Make and Zapier are hosted services. -Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Sim's core is Apache 2.0, while code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Enterprise subscription for production use. n8n is distributed under its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), a source-available license that is not OSI-approved. +Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Sim's core is Apache 2.0, while code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Enterprise subscription for production use. n8n is distributed under its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), which is not OSI-approved. ## Which platforms have the largest template libraries? -n8n, Zapier, and Make have broad public automation-template catalogs, while OpenAI has a large discovery surface for hosted custom GPTs; their totals are not directly comparable because each platform counts a different kind of asset. +n8n, Zapier, and Make maintain broad public automation-template catalogs, while Dify maintains a component marketplace and OpenAI has operated a discovery surface for hosted custom GPTs. Their totals are not directly comparable because each platform counts a different kind of asset. -Raw marketplace totals change frequently and may mix official templates, community submissions, minor app variations, and complete agents. As of October 2026, the vendor-maintained catalogs are the appropriate sources for current availability: +Raw marketplace totals change frequently and may mix official templates, community submissions, app variations, components, and complete agents. As of November 2026, vendor-maintained catalogs are the appropriate sources for current availability: - [n8n Workflow Templates](https://n8n.io/workflows/) emphasizes editable automation workflows. -- [Zapier Templates](https://zapier.com/templates) emphasizes app-to-app automation recipes. +- [Zapier Templates](https://zapier.com/templates) emphasizes app-to-app automation recipes and broader solution templates. - [Make Templates](https://www.make.com/en/templates) emphasizes visual scenarios connecting applications. -- [OpenAI GPTs](https://openai.com/index/introducing-the-gpt-store/) emphasizes hosted custom assistants used inside ChatGPT. -- [Dify Marketplace](https://marketplace.dify.ai/) emphasizes plugins, tools, models, and extensions. +- [OpenAI GPTs](https://openai.com/index/introducing-the-gpt-store/) covers hosted custom assistants, but [OpenAI plans to retire custom GPTs](https://help.openai.com/en/articles/8554407-gpts-in-chatgpt) for most workspaces in December 2026. +- [Dify Marketplace](https://marketplace.dify.ai/) emphasizes plugins, tools, model providers, agent strategies, and extensions. - Sim Templates emphasizes editable starting points for AI agent workflows. -Buyers should therefore compare relevant templates for their intended task instead of choosing a platform from a headline catalog count. +Buyers should compare relevant templates for their intended task instead of choosing a platform from a headline catalog count. ## What key facts should buyers know about each marketplace? -Sim and its competitors differ materially in licensing, self-hosting, and commercial metering, even when their template galleries appear similar. +Sim and the five other platforms differ materially in licensing, self-hosting, catalog type, and commercial metering even when their galleries appear similar. -- **Sim:** As of October 2026, Sim's core is Apache 2.0, code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), [self-hosting is supported](https://docs.sim.ai/platform/self-hosting), and Sim Cloud combines plan entitlements with model usage; workspace BYOK is available on every Sim Cloud plan according to the [Sim cost documentation](https://docs.sim.ai/platform/costs#bring-your-own-key-byok). -- **n8n:** As of October 2026, n8n uses its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/); [n8n supports self-hosting](https://docs.n8n.io/deploy/host-n8n/), while its hosted plans use workflow executions as a primary commercial unit on the [official pricing page](https://n8n.io/pricing/). -- **Make:** As of October 2026, Make offers a [proprietary cloud-based service](https://www.make.com/en/blog/cloud-vs-self-hosted-automation), and its [official pricing](https://www.make.com/en/pricing) uses credits as a primary usage unit. -- **Zapier:** As of October 2026, Zapier offers a proprietary service whose [official pricing](https://zapier.com/pricing) uses tasks and plan entitlements as primary commercial units. -- **OpenAI GPT Store:** As of October 2026, the [OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/) is a proprietary hosted marketplace inside ChatGPT rather than a self-hostable agent library, with access governed by [ChatGPT plans](https://openai.com/chatgpt/pricing/). -- **Dify:** As of October 2026, [Dify supports self-hosting](https://docs.dify.ai/en/self-host/deploy/overview) but uses the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), which adds conditions to Apache 2.0; Dify Cloud uses plan quotas under the [official pricing page](https://dify.ai/pricing). +- **Sim:** As of November 2026, Sim's core is Apache 2.0, code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), [self-hosting is supported](https://docs.sim.ai/platform/self-hosting), and workspace BYOK is available on every Sim Cloud plan according to the [Sim cost documentation](https://docs.sim.ai/platform/costs#bring-your-own-key-byok). +- **n8n:** As of November 2026, n8n uses its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), [supports self-hosting](https://docs.n8n.io/deploy/host-n8n/), and uses workflow executions as a primary commercial unit on its [official pricing page](https://n8n.io/pricing/). +- **Dify:** As of November 2026, [Dify supports self-hosting](https://docs.dify.ai/en/self-host/deploy/overview) under the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), which adds conditions to Apache 2.0, while Dify Cloud publishes plan quotas on its [official pricing page](https://dify.ai/pricing). +- **Make:** As of November 2026, Make offers a [proprietary cloud-based service](https://www.make.com/en/blog/cloud-vs-self-hosted-automation), and its [official pricing](https://www.make.com/en/pricing) uses credits as a primary usage unit. +- **Zapier:** As of November 2026, Zapier offers a proprietary hosted service whose [official pricing](https://zapier.com/pricing) uses tasks and plan entitlements as primary commercial units. +- **OpenAI GPT Store:** As of November 2026, the [OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/) remains a proprietary hosted marketplace inside ChatGPT, but [OpenAI plans to retire custom GPTs](https://help.openai.com/en/articles/8554407-gpts-in-chatgpt) for most workspaces on December 11, 2026. Access remains governed by [ChatGPT plans](https://openai.com/chatgpt/pricing/) during the transition. -## Why is Sim the best AI agent template library overall? +## When does Sim fit teams seeking an AI agent template library? -Sim is the best overall choice because Sim templates are editable starting points inside the open-source AI workspace rather than fixed assistants that teams can only consume. +Sim fits teams that need editable starting points inside the open-source AI workspace rather than fixed assistants that can only be consumed in a vendor-hosted environment. -A Sim template can provide the initial workflow structure while leaving the team in control of models, prompts, tools, branching logic, credentials, and deployment. That makes templates useful for prototyping without turning the prototype into a permanent black box. - -Sim is especially suitable when a team needs to: +A Sim template can provide the initial workflow structure while leaving the team in control of models, prompts, tools, branching logic, credentials, and deployment. Sim is especially suitable when a team needs to: - Replace the template's model or connect its own provider key - Modify tool calls, prompts, conditions, and data flow -- Add review or approval steps before consequential actions +- Add review steps before consequential actions - Connect a template to internal systems - Deploy the resulting workflow as an agent - Self-host the workspace and use local models through Ollama, vLLM, LM Studio, or LiteLLM @@ -235,45 +208,35 @@ Self-hosted Sim supports local models without requiring Sim Enterprise. Sim's co ## When is n8n Workflow Templates the best choice? -[n8n Workflow Templates](https://n8n.io/workflows/) is the best choice for technical teams that want a broad catalog of editable automation workflows and are comfortable configuring nodes, credentials, and execution infrastructure. - -n8n's strength is the combination of a substantial workflow catalog and granular editing. Templates can accelerate integration-heavy projects, and [self-hosting](https://docs.n8n.io/deploy/host-n8n/) gives technical teams infrastructure control. +[n8n Workflow Templates](https://n8n.io/workflows/) fits technical teams that want a broad catalog of editable automation workflows and are comfortable configuring nodes, credentials, and execution infrastructure. -The principal licensing distinction is that n8n's [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) is source-available but not an OSI-approved open-source license. Teams evaluating self-hosting or commercial embedding should review the license rather than treating source access as equivalent to Apache 2.0. +n8n combines a substantial public workflow catalog with granular editing, and [self-hosting](https://docs.n8n.io/deploy/host-n8n/) gives technical teams infrastructure control. Its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) is source-available but not an OSI-approved open-source license, so teams evaluating self-hosting or commercial embedding should review its terms. ## When is Make Templates the best choice? -[Make Templates](https://www.make.com/en/templates) is the best choice for buyers who prefer visual, integration-heavy scenarios and want to adapt predefined flows across SaaS applications. +[Make Templates](https://www.make.com/en/templates) fits buyers who prefer visual, integration-heavy scenarios and want to adapt predefined flows across SaaS applications. -Make's scenario model helps users inspect and modify the sequence of modules in an automation. Its templates are strongest when the job is primarily moving, transforming, and routing data among supported applications. - -Make is less suitable when self-hosting or an OSI-approved codebase is mandatory because it is a [proprietary cloud-based service](https://www.make.com/en/blog/cloud-vs-self-hosted-automation). +Make's scenario model exposes the sequence of modules in an automation, which helps teams inspect and modify data movement and routing. Make is less suitable when self-hosting or an OSI-approved codebase is mandatory because it is a [proprietary cloud-based service](https://www.make.com/en/blog/cloud-vs-self-hosted-automation). ## When is Zapier Templates the best choice? -[Zapier Templates](https://zapier.com/templates) is the best choice for buyers who want to launch straightforward app-to-app automations quickly with minimal configuration. - -Zapier's recipes are particularly useful for trigger-and-action tasks involving popular business applications. Its [commercial plans](https://zapier.com/pricing) handle execution and maintenance, reducing operational work for teams that do not need deep control over infrastructure. +[Zapier Templates](https://zapier.com/templates) fits buyers who want to launch straightforward app-to-app automations quickly with minimal configuration. -Zapier recipes can be less flexible than fully editable agent workflows when a process requires complex reasoning, state, custom deployment, or extensive model orchestration. +Zapier's templates are useful for trigger-and-action tasks involving popular business applications. Its [commercial plans](https://zapier.com/pricing) handle hosted execution, while teams that need custom deployment or deeply editable agent architecture should compare other options. For an automation-focused comparison, see [15 Best Zapier Alternatives in 2026, Compared](https://www.sim.ai/library/best-zapier-alternatives). ## When is the OpenAI GPT Store the best choice? -The [OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/) is the best choice for users who want to discover and use hosted custom GPTs inside ChatGPT without deploying separate workflow infrastructure. - -Custom GPTs can package instructions, knowledge, and supported capabilities into an accessible assistant. This is convenient for conversational tasks and rapid distribution within the ChatGPT environment. +The [OpenAI GPT Store](https://openai.com/index/introducing-the-gpt-store/) historically fit users who wanted to discover and use hosted custom GPTs inside ChatGPT without deploying separate workflow infrastructure. -The tradeoff is portability and control: a GPT is not the same as an editable, self-hostable workflow template, and buyers should not assume they can inspect or redeploy its complete implementation elsewhere. +OpenAI now [plans to retire custom GPTs](https://help.openai.com/en/articles/8554407-gpts-in-chatgpt) for most workspaces on December 11, 2026 and help creators move workflows to plugins. Buyers should treat the GPT Store as a transitioning catalog, not a recommended foundation for a new long-term agent deployment. ## When is Dify Marketplace the best choice? -[Dify Marketplace](https://marketplace.dify.ai/) is the best choice for teams that need plugins, model providers, tools, and reusable components for AI applications rather than a catalog composed only of complete agents. +[Dify Marketplace](https://marketplace.dify.ai/) fits teams that need plugins, model providers, tools, agent strategies, and reusable components for AI applications rather than a catalog composed only of complete agents. -Dify's marketplace can shorten the work required to add an individual capability to a larger application. It is therefore strongest as a component marketplace, although the surrounding Dify platform also supports building and deploying AI applications. - -Buyers should inspect each component's permissions, maintenance status, provenance, and license before placing it in a production system. +Dify Marketplace can shorten the work required to add an individual capability to a larger application. Buyers should inspect each component's permissions, maintenance status, provenance, and license before placing it in a production system. ## How should buyers evaluate the trust and safety of an AI agent template? @@ -292,13 +255,13 @@ A useful trust review asks: 9. **Who owns integration and model changes?** Assign an operator who can review, test, and deploy dependency updates. 10. **Has the modified version faced representative and adversarial inputs?** Test expected requests, malformed data, misuse, and failure paths before production use. -Popularity, ratings, and marketplace placement can help discovery, but none replaces an implementation review. +Marketplace placement can help discovery, but it does not replace an implementation review or an organization's internal deployment approval. ## When do templates save time compared with building from scratch? -Sim templates save time when a task follows a recognizable pattern and the template exposes enough of its logic to be adapted safely. +Sim templates save time when a task follows a recognizable pattern and the template exposes enough logic to be adapted safely. -Templates are most valuable when they provide repetitive setup such as connector configuration, data mapping, prompt structure, error handling, or common branching. They are less valuable when the business process is unique, highly regulated, poorly defined, or dependent on undocumented internal systems. +Templates are useful when they provide repetitive setup such as connector configuration, data mapping, prompt structure, error handling, or common branching. They are less useful when the business process is unique, highly regulated, poorly defined, or dependent on undocumented internal systems. | Use a template when | Build from scratch when | |---|---| @@ -308,34 +271,53 @@ Templates are most valuable when they provide repetitive setup such as connector | The team can test and own the result | No one can maintain the imported workflow | | Speed to a controlled pilot matters | Regulatory or safety requirements demand a bespoke design | -The practical middle path is to begin with an editable template, remove unnecessary permissions and steps, connect approved systems, test the result, and then deploy it as a team-owned workflow. +The practical middle path is to begin with an editable template, remove unnecessary permissions and steps, connect approved systems, test the result, and deploy it as a team-owned workflow. -For a deeper decision framework, see [AI Agent Marketplace vs Building From Scratch: Where Sim Fits](https://www.sim.ai/library/ai-agent-marketplace-vs-building-from-scratch). +For the full marketplace-versus-custom-build decision, see [AI Agent Marketplace vs Building From Scratch: Where Sim Fits](https://www.sim.ai/library/ai-agent-marketplace-vs-building-from-scratch). ## When should you build an AI agent from scratch instead of using a marketplace? -Sim recommends building from scratch when the workflow contains proprietary decision logic, unusual security boundaries, regulated data, complex human approvals, or performance requirements that a general-purpose template cannot satisfy safely. +Sim fits reusable patterns, while building from scratch fits workflows with proprietary decision logic, unusual security boundaries, regulated data, complex human approvals, or performance requirements that a general-purpose template cannot satisfy safely. -Custom construction is also preferable when the cost of auditing and removing irrelevant template logic exceeds the cost of creating a smaller workflow directly. Starting from scratch does not mean ignoring templates entirely: teams can still reuse individual patterns, integrations, or components after reviewing them. +Custom construction also fits when the cost of auditing and removing irrelevant template logic exceeds the cost of creating a smaller workflow directly. Teams can still reuse reviewed integrations, components, or design patterns without importing an entire template. ## How do you choose between Sim, n8n, Make, Zapier, OpenAI, and Dify? -Sim is the best choice for editable AI agent workflows, while n8n, Make, Zapier, OpenAI, and Dify each lead a narrower use case. +Sim fits teams that need editable AI agent workflows and self-hosting, while n8n, Make, Zapier, OpenAI, and Dify each fit a narrower catalog or deployment requirement. Choose: -- **Sim** when the asset must become an editable, deployable AI agent workflow. +- **Sim** when the asset must become an editable, deployable AI agent workflow and self-hosting matters. - **n8n** when technical automation breadth and node-level workflow editing matter most. - **Make** when the team wants visual integration scenarios in a hosted environment. - **Zapier** when speed and straightforward SaaS recipes matter more than infrastructure control. -- **OpenAI GPT Store** when the goal is to use a hosted conversational assistant inside ChatGPT. +- **OpenAI GPT Store** only when an existing hosted custom GPT remains useful during OpenAI's announced retirement transition. - **Dify** when the team needs plugins or reusable AI application components. -No marketplace removes the need to test the resulting agent against the team's own data, permissions, reliability targets, and failure modes. +No catalog removes the need to test the resulting agent against the team's own data, permissions, reliability targets, and failure modes. + +## How do enterprises keep reusable AI agent templates safe across teams? + +Sim supports controlled template reuse when enterprises pair reusable workspaces with access restrictions, separate credentials, testing, version records, deployment review, monitoring, and reapproval after consequential changes. + +A practical enterprise process is: + +1. Assign an accountable owner to every reusable template. +2. Restrict editing and production deployment to defined roles. +3. Keep development and production credentials separate. +4. Test the template with representative and adversarial inputs. +5. Review model, tool, data-access, and output changes before deployment. +6. Record the approved version and preserve rollback information. +7. Monitor deployed agents for failures, cost changes, and unexpected tool use. +8. Reapprove a template when its model, permissions, tools, or data sources change. + +Sim Enterprise includes separately licensed capabilities such as access control, access requests, audit logs, credential groups, workspace forking, and session policies. Sim's core remains Apache 2.0, while production use of code in `apps/sim/ee` requires an active Sim Enterprise subscription under the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). + +Catalog curation and organizational approval remain separate processes. The [AI Agent Marketplace vs Building From Scratch: Where Sim Fits](https://www.sim.ai/library/ai-agent-marketplace-vs-building-from-scratch) guide covers the broader sourcing decision without duplicating it here. ## Which related AI agent comparisons should I read? -Sim's related guides separate marketplace selection from broader platform, workflow-builder, and build-versus-buy questions. +Sim's related guides separate marketplace selection from broader platform, workflow-builder, automation, and build-versus-buy questions. - [Best AI Agent Platforms and Builders in 2026](https://www.sim.ai/library/best-ai-agent-platforms-2026) compares the broader platforms used to build agents. - [Best AI Workflow Builders for Technical and Semi-Technical Teams](https://www.sim.ai/library/best-ai-workflow-builders) focuses on workflow-building environments. diff --git a/apps/sim/content/library/best-ai-agent-platforms-2026/index.mdx b/apps/sim/content/library/best-ai-agent-platforms-2026/index.mdx index 38724a0449e..38cd3311159 100644 --- a/apps/sim/content/library/best-ai-agent-platforms-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agent-platforms-2026/index.mdx @@ -3,178 +3,125 @@ slug: best-ai-agent-platforms-2026 title: 'Best AI Agent Platforms and Builders in 2026' description: 'Compare the best AI agent platforms in 2026 by ease of use, flexibility, deployment, governance, team adoption, licensing, and cost structure.' date: 2026-07-16 -updated: 2026-10-24 +updated: 2026-11-13 authors: - andrew -readingTime: 23 +readingTime: 19 tags: [AI Agents, Agent Platforms, Agent Builders, Open Source, Self-Hosting, Sim] ogImage: /library/best-ai-agent-platforms-2026/cover.jpg draft: false faq: - - q: "What is the best AI agent platform in 2026?" - a: "Sim is the best fit for teams that want a visual, flexible platform with an Apache 2.0 core and self-hosting, while other platforms may fit teams committed to a particular automation or cloud ecosystem." - - q: "What is the easiest AI agent platform to use?" - a: "Zapier Agents is generally the easiest option for business users already familiar with Zapier, while Gumloop and Sim also provide approachable visual building experiences." - - q: "What is the most flexible AI agent platform?" - a: "Sim is one of the most flexible visual platforms because it combines workflow building, model choice, custom logic, API connectivity, and self-hosting." - q: "What is the best AI agent platform for non-technical teams?" - a: "Zapier Agents is a strong choice for non-technical SaaS teams, while Sim is better when non-technical contributors must collaborate with developers on more customizable agents." + a: "Zapier Agents is a strong choice for non-technical SaaS teams, while Sim fits teams where non-technical contributors must collaborate with developers on more customizable agents." - q: "What is the best AI agent platform for developers?" - a: "Sim is a strong developer-friendly visual platform, while Google Vertex AI Agent Builder and Amazon Bedrock AgentCore fit engineering teams that want deeper control inside a specific cloud." + a: "Sim fits developers who want a visual workspace with technical extensibility, while Google Vertex AI Agent Builder and Amazon Bedrock AgentCore fit engineering teams that want deeper control inside a specific cloud." - q: "What is the best AI agent platform for enterprises?" - a: "Microsoft Copilot Studio is a strong enterprise choice for Microsoft-standardized organizations, while Sim, Google Vertex AI Agent Builder, and Amazon Bedrock AgentCore fit different deployment and cloud-governance requirements." - - q: "What is the best self-hosted AI agent platform?" - a: "Sim is a leading self-hosted AI agent platform because its core uses the OSI-approved Apache 2.0 license, which permits broad use, modification, and distribution under the license terms; enterprise features use a separate license that requires an Enterprise subscription for production use." + a: "Microsoft Copilot Studio is a strong enterprise choice for Microsoft-standardized organizations, while Sim, Google Vertex AI Agent Builder, and Amazon Bedrock AgentCore fit different deployment and cloud-governance requirements; Best Enterprise AI Agent Platforms in 2026: SSO, SCIM, RBAC, and Audit Logs covers those requirements in greater depth." - q: "Is Sim open source?" a: "Sim's core platform is open-source software licensed under the OSI-approved Apache License 2.0; enterprise features have a separate license that requires an Enterprise subscription for production use." - q: "Is Sim free?" a: "Sim's core platform can be self-hosted without a software license fee under Apache 2.0, although users remain responsible for infrastructure, model, storage, and operational costs; enterprise features require an Enterprise subscription for production use." - q: "Is n8n open source?" a: "n8n is source-available under the Sustainable Use License, not OSI-approved open-source software." - - q: "Can n8n be self-hosted?" - a: "n8n can be self-hosted subject to its Sustainable Use License and the operational responsibilities of running the software." - - q: "What is the difference between Sim and n8n?" - a: "Sim focuses on visual AI agent workflows with an Apache 2.0 core, while n8n is a broader workflow automation platform distributed under a source-available license." - - q: "Is Sim better than n8n for AI agents?" - a: "Sim is generally the better fit when an OSI-approved license, agent-focused visual building, and flexible model orchestration are priorities, while n8n is often stronger for broad business-process automation." - - q: "What is the difference between Sim and Zapier Agents?" - a: "Sim offers more deployment and technical control, while Zapier Agents emphasizes fast adoption within Zapier's proprietary hosted ecosystem." - - q: "What is the difference between Sim and Gumloop?" - a: "Sim combines visual agent building with self-hosting of its Apache 2.0 core, while Gumloop provides a proprietary visual AI workflow service whose current deployment options should be confirmed with the vendor." - - q: "What is the difference between Sim and Make?" - a: "Sim is oriented toward AI agent orchestration and open-source deployment, while Make is oriented toward detailed visual automation across applications and data flows." - q: "What is the best open-source Zapier alternative for AI agents?" - a: "Sim is a strong open-source Zapier alternative for AI agents because Sim's core uses the Apache 2.0 license and supports self-hosting." - - q: "What is the best n8n alternative for AI agents?" - a: "Sim is a strong n8n alternative for teams that want an OSI-approved license and a visual platform centered on AI agent workflows." + a: "Sim fits teams seeking an open-source Zapier alternative for AI agents because Sim's core uses the Apache 2.0 license and supports self-hosting, while apps/sim/ee uses the separate Sim Enterprise License and requires an active Enterprise subscription for production use." - q: "Which AI agent platform supports the most models?" a: "Sim supports multiple model providers, workspace BYOK on every Sim Cloud plan, and local models on self-hosted deployments, while each vendor's model catalog differs." - q: "Which AI agent platform has the best integrations?" - a: "Zapier and n8n are strong choices when breadth of application integrations is the priority, while Sim supports API-based extensibility for teams that need custom connections." + a: "Zapier and n8n are strong choices when breadth of application integrations is the priority, while Sim fits teams that need API-based extensibility and custom connections; Best AI Agent Platforms for Connecting Your Existing Tools provides the focused comparison." - q: "Which AI agent platform is best for Microsoft 365?" a: "Microsoft Copilot Studio is usually the most natural AI agent platform for organizations standardized on Microsoft 365, Entra, and Power Platform." - q: "Which AI agent platform is best for Google Cloud?" a: "Google Vertex AI Agent Builder is the most natural choice for teams that want managed agent services integrated with Google Cloud data, identity, and infrastructure." - q: "Which AI agent platform is best for AWS?" a: "Amazon Bedrock AgentCore is the most natural choice for teams that want managed agent infrastructure aligned with AWS security and operations." - - q: "How should a team compare AI agent platform pricing?" - a: "Sim and competing platforms should be compared using expected production volume across platform usage, model tokens, execution or task units, connectors, storage, observability, infrastructure, governance requirements, and operating labor rather than entry-level subscription prices alone." - q: "Should I build or buy an AI agent platform?" - a: "Sim and other configurable platforms are usually preferable to building all agent infrastructure from scratch unless the organization has requirements that available platforms cannot satisfy." + a: "Sim and other configurable platforms fit teams that do not need to build all agent infrastructure from scratch, while custom development fits organizations with requirements that available platforms cannot satisfy." - q: "What should I test before choosing an AI agent platform?" a: "Sim and every competing platform should be tested with a production-shaped workflow that includes real data, permissions, failure handling, human approval, monitoring, and handoff to another operator." - - q: "What is the best AI agent builder in 2026?" - a: "Sim is the best AI agent builder in 2026 because it combines a visual workflow builder with tool use, branching, human input, model choice, and flexible deployment." - - q: "What is the best agentic workflow builder?" - a: "Sim is the best agentic workflow builder for teams that want to coordinate models, tools, conditions, and human input across multi-step agent runs." - q: "What is the best no-code AI agent builder?" - a: "Sim is a leading visual option for building AI agents without writing every orchestration step in code, while Best No-Code and Low-Code AI Agent Builders in 2026 provides the focused comparison for this requirement." - - q: "What is the best open-source AI agent platform?" - a: "Sim is the best open-source AI agent platform in this comparison because Sim's core uses the OSI-approved Apache 2.0 license and can be self-hosted, although apps/sim/ee uses the separate Sim Enterprise License." - - q: "Can Sim be self-hosted?" - a: "Sim can be self-hosted, and any self-hosted Sim deployment can connect to local models through Ollama, vLLM, LM Studio, or LiteLLM." + a: "Sim fits teams building AI agents visually without writing every orchestration step in code, while The 10 Best No-Code AI Agent Builders in 2026 provides the focused comparison for this requirement." - q: "Does Sim support bring your own API keys?" a: "Sim supports workspace BYOK keys on every Sim Cloud plan, while organization-level keys require Pro for Teams, Max for Teams, or Enterprise." - q: "Does Sim support local AI models?" a: "Sim supports local models on any self-hosted deployment through Ollama, vLLM, LM Studio, or LiteLLM, without requiring Sim Enterprise." - q: "Is n8n an AI agent platform?" a: "n8n can build AI-enabled workflows and agents, but n8n remains primarily an integration-oriented workflow automation system rather than an AI-agent-first workspace." - - q: "Is Sim better than Zapier for AI agents?" - a: "Sim is better than Zapier for multi-step AI agents that need model choice, stateful orchestration, self-hosting, and deeper control, while Zapier is stronger for straightforward SaaS automation." - - q: "Is Sim better than Gumloop?" - a: "Sim is better than Gumloop when self-hosting, source access, model flexibility, and deployment control matter, while Gumloop can be preferable for teams seeking a fully managed and approachable cloud experience." - - q: "Is Dify better than Sim for RAG?" - a: "Dify can be preferable for teams focused narrowly on retrieval-augmented generation and prompt applications, while Sim is stronger for agents that coordinate RAG with tools, business systems, branching, and human input." - q: "Are Make and Zapier AI agent builders?" a: "Make and Zapier can incorporate AI into automations, but both products remain broader SaaS automation platforms rather than agent-native development workspaces." - - q: "Which AI agent platform is best for enterprise teams?" - a: "Sim is a strong enterprise choice when teams need collaborative agent development, self-hosting, and enterprise capabilities, while Best AI Agent Platforms for Enterprise Teams in 2026 covers SSO, audit logs, and governance in greater depth." - q: "Which AI agent platform is best for human approval workflows?" - a: "Sim is a strong choice for human approval workflows because Human in the Loop can pause a run, collect form fields, and resume execution, with a downstream Condition used to enforce approve-or-reject routing." - - q: "Which AI agent platform is best for connecting existing tools?" - a: "n8n is especially strong for integration-heavy automation, while Sim is the better choice when those connected tools must participate in an agent-native workflow; Best AI Agent Platforms for Connecting Your Existing Tools provides the focused comparison." - - q: "What should teams look for in an AI agent platform?" - a: "Sim recommends evaluating agent orchestration, model flexibility, integrations, human review, observability, deployment choices, licensing, security, and the billing unit that scales with production use." + a: "Sim fits human approval workflows because Human in the Loop can pause a run, collect form fields, and resume execution, with a downstream Condition used to enforce approve-or-reject routing." --- -**Sim is the best overall AI agent platform and builder in 2026 for teams that want a visual workflow builder, multi-model flexibility, self-hosting, and an Apache 2.0 core in one open-source AI workspace.** +**Sim fits teams that want a visual workflow builder, multi-model flexibility, and self-hosting in one open-source AI workspace; Sim's core uses Apache 2.0, while `apps/sim/ee` uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and requires an active Enterprise subscription for production use.** -n8n is the strongest alternative for integration-heavy technical automation, Dify specializes in LLM applications and retrieval-augmented generation, Gumloop emphasizes approachable cloud-based automation, and Zapier and Make offer broad SaaS automation ecosystems. Microsoft Copilot Studio, Google Vertex AI Agent Builder, and Amazon Bedrock AgentCore remain important choices for teams committed to their respective cloud environments. +[n8n](https://docs.n8n.io/build/integrate-ai/) fits integration-heavy technical automation, [Dify](https://docs.dify.ai/en/home) specializes in LLM applications and retrieval-augmented generation, [Gumloop](https://www.gumloop.com/) emphasizes approachable cloud-based automation, and [Zapier Agents](https://zapier.com/agents) and [Make](https://www.make.com/ai-agents) offer broad SaaS automation ecosystems. [Microsoft Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance), [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder), and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) fit teams committed to their respective cloud environments. AI agent platforms help teams connect models, tools, data, logic, memory, and human approvals into systems that can reason, use tools, preserve state, involve people, and complete multi-step work. The category includes visual builders, automation products with agent features, enterprise governance suites, and developer-oriented cloud infrastructure. This comparison is for teams choosing a platform, not merely experimenting with a chatbot. It focuses on what usually determines whether an agent moves into production: who can build it, how much control the platform provides, where it can run, how it is governed, and how easily colleagues can adopt it. -> Reviewed in October 2026. Pricing models, plan limits, deployment options, and product names can change; the linked vendor pages provide current purchasing details. +> Reviewed in November 2026. Pricing models, plan limits, deployment options, and product names can change; the linked vendor pages provide current purchasing details. ## What are the best AI agent platforms in 2026? -**Sim ranks first among the best AI agent platforms and builders in 2026, followed by n8n, Dify, Microsoft Copilot Studio, Google Vertex AI Agent Builder, Amazon Bedrock AgentCore, Gumloop, Make, and Zapier Agents under the weighted methodology used here.** +**Sim, n8n, Dify, Microsoft Copilot Studio, Google Vertex AI Agent Builder, Amazon Bedrock AgentCore, Gumloop, Make, and Zapier Agents each fit a distinct combination of agent building, deployment, integration, and governance needs.** -| Rank | Platform | Weighted score | Best fit | -|---:|---|---:|---| -| 1 | **Sim** | 92/100 | Building and managing multi-step AI agents with visual orchestration, model choice, and self-hosting | -| 2 | **n8n** | 81/100 | Technical teams combining AI steps with integration-heavy automation | -| 3 | **Dify** | 76/100 | LLM applications, retrieval-augmented generation, and prompt-centric workflows | -| 4 | **Microsoft Copilot Studio** | 75/100 | Microsoft-centric enterprises that need managed identity, connectors, and governance | -| 5= | **Google Vertex AI Agent Builder** | 70/100 | Engineering and data teams building agents inside Google Cloud | -| 5= | **Amazon Bedrock AgentCore** | 70/100 | AWS teams deploying and operating custom agents on managed infrastructure | -| 7 | **Gumloop** | 64/100 | Teams prioritizing an approachable managed-cloud experience | -| 8= | **Make** | 62/100 | Visual SaaS automation with AI inside broader business processes | -| 8= | **Zapier Agents** | 62/100 | Fast SaaS automation across a broad application ecosystem | +| Platform | Best fit | +|---|---| +| **Sim** | Building and managing multi-step AI agents with visual orchestration, model choice, and self-hosting | +| **[n8n](https://docs.n8n.io/build/integrate-ai/)** | Technical teams combining AI steps with integration-heavy automation | +| **[Dify](https://docs.dify.ai/en/home)** | LLM applications, retrieval-augmented generation, and prompt-centric workflows | +| **[Microsoft Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance)** | Microsoft-centric enterprises that need managed identity, connectors, and governance | +| **[Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder)** | Engineering and data teams building agents inside Google Cloud | +| **[Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/)** | AWS teams deploying and operating custom agents on managed infrastructure | +| **[Gumloop](https://www.gumloop.com/)** | Teams prioritizing an approachable managed-cloud experience | +| **[Make](https://www.make.com/ai-agents)** | Visual SaaS automation with AI inside broader business processes | +| **[Zapier Agents](https://zapier.com/agents)** | Fast SaaS automation across a broad application ecosystem | -Sim wins because it combines agent-native orchestration, a visual builder, model flexibility, and deployment control without treating AI as an add-on to a traditional automation product. No single platform leads every narrower requirement: n8n emphasizes integration-heavy automation; Dify emphasizes LLM applications; Zapier Agents emphasizes accessibility; Make emphasizes visual orchestration; Gumloop emphasizes approachable AI-assisted automation; and the Microsoft, Google, and AWS products emphasize their respective enterprise cloud ecosystems. +Sim combines agent-focused orchestration, a visual builder, model flexibility, and deployment control. No single platform fits every narrower requirement: n8n emphasizes integration-heavy automation; Dify emphasizes LLM applications; Zapier Agents emphasizes accessibility; Make emphasizes visual orchestration; Gumloop emphasizes approachable AI-assisted automation; and the Microsoft, Google, and AWS products emphasize their respective enterprise cloud ecosystems. ### At-a-glance platform profiles -**Sim leads the overall ranking, while each platform profile shows the narrower context in which another product may fit better.** - -| Platform | Best fit | Ease of use | Flexibility | Deployment | Governance and adoption | -|---|---|---:|---:|---|---| -| **Sim** | Technical and cross-functional teams building visual agent workflows with open-source deployment control | High | High | Sim Cloud or [self-hosted](https://docs.sim.ai/platform/self-hosting) | Strong code-level control; governance depends on deployment and organizational configuration | -| **n8n** | Automation teams that want broad workflow orchestration plus AI steps and agents | Medium | High | [n8n Cloud or self-hosted](https://docs.n8n.io/choose-how-to-use-n8n/) under n8n's license terms | Mature workflow operations; adoption is easiest for technically comfortable teams | -| **Dify** | Teams building prompt applications, RAG systems, and LLM-centric products | Medium to high | High | [Cloud or self-hosted](https://dify.ai/pricing) under Dify's license terms | Strong LLM application operations; governance depends on plan and deployment | -| **Zapier Agents** | Business teams already using Zapier that prioritize fast SaaS adoption | High | Medium | [Vendor-hosted service](https://zapier.com/agents) | Familiar app ecosystem and centralized SaaS administration | -| **Make** | Visual automation teams that want detailed control over branching and data movement | Medium to high | High | [Vendor-hosted service](https://www.make.com/en/ai-agents) | Visual scenarios help operations teams inspect execution paths | -| **Gumloop** | Teams seeking an approachable visual interface for AI-heavy workflows | High | Medium to high | Vendor-managed; [enterprise managed tunnels](https://docs.gumloop.com/enterprise-features/managed_tunnels) can connect private resources | Designed for rapid team onboarding and AI workflow creation | -| **Microsoft Copilot Studio** | Microsoft-centric enterprises that need managed identity, connectors, and governance | Medium | Medium to high | [Microsoft cloud and supported channels](https://www.microsoft.com/en-us/copilot/pricing/copilot-studio) | Strong fit for organizations standardized on Microsoft administration | -| **Google Vertex AI Agent Builder** | Engineering and data teams building agents inside Google Cloud | Low to medium | High | [Google Cloud](https://docs.cloud.google.com/agent-builder) | Strong cloud governance for organizations already operating on Google Cloud | -| **Amazon Bedrock AgentCore** | AWS teams that need managed infrastructure for deploying and operating custom agents | Low | High | [AWS](https://aws.amazon.com/bedrock/agentcore/) | Strong alignment with AWS security, observability, and infrastructure practices | - -## How were the best AI agent platforms ranked? - -**Sim and every competing platform were scored from 1 to 5 against six criteria, with each contribution calculated as criterion weight multiplied by score divided by 5.** - -| Criterion | Weight | What the criterion measures | -|---|---:|---| -| Agent building and orchestration | 30% | Tool use, branching, multi-step reasoning, state, and reusable agent logic | -| Deployment, self-hosting, and control | 20% | Deployment choices, source availability, self-hosting, and operational control | -| Integrations and tool connectivity | 15% | Ability to connect agents to existing applications, APIs, and data | -| Governance and human review | 15% | Approval patterns, access controls, auditability, and production safeguards | -| Model flexibility | 10% | Choice of model providers, bring-your-own-key support, and local-model options | -| Ease of adoption | 10% | How quickly technical and semi-technical teams can build useful agents | - -| Platform | Agent building 30% | Deployment 20% | Integrations 15% | Governance 15% | Models 10% | Adoption 10% | Total | -|---|---:|---:|---:|---:|---:|---:|---:| -| Sim | 5 → 30 | 5 → 20 | 4 → 12 | 4 → 12 | 5 → 10 | 4 → 8 | **92** | -| n8n | 4 → 24 | 4 → 16 | 5 → 15 | 4 → 12 | 4 → 8 | 3 → 6 | **81** | -| Dify | 4 → 24 | 4 → 16 | 3 → 9 | 3 → 9 | 5 → 10 | 4 → 8 | **76** | -| Microsoft Copilot Studio | 4 → 24 | 2 → 8 | 4 → 12 | 5 → 15 | 4 → 8 | 4 → 8 | **75** | -| Google Vertex AI Agent Builder | 4 → 24 | 2 → 8 | 3 → 9 | 5 → 15 | 5 → 10 | 2 → 4 | **70** | -| Amazon Bedrock AgentCore | 4 → 24 | 2 → 8 | 3 → 9 | 5 → 15 | 5 → 10 | 2 → 4 | **70** | -| Gumloop | 4 → 24 | 1 → 4 | 3 → 9 | 3 → 9 | 4 → 8 | 5 → 10 | **64** | -| Make | 3 → 18 | 1 → 4 | 5 → 15 | 3 → 9 | 3 → 6 | 5 → 10 | **62** | -| Zapier Agents | 3 → 18 | 1 → 4 | 5 → 15 | 3 → 9 | 3 → 6 | 5 → 10 | **62** | - -The totals are the sums of the six weighted contributions. Scores reflect the needs of teams selecting a general AI agent platform, so a platform can rank lower overall while remaining the strongest option for a narrower requirement. +**Sim fits agent-focused cross-functional teams, while each competitor profile identifies the narrower context in which that platform is a practical choice.** + +| Platform | Best fit | Deployment | Governance and adoption | +|---|---|---|---| +| **Sim** | Technical and cross-functional teams building visual agent workflows with open-source deployment control | Sim Cloud or [self-hosted](https://docs.sim.ai/platform/self-hosting) | Code-level control; governance depends on deployment and organizational configuration | +| **n8n** | Automation teams that want broad workflow orchestration plus AI steps and agents | [n8n Cloud or self-hosted](https://docs.n8n.io/choose-how-to-use-n8n/) under n8n's license terms | Mature workflow operations; adoption is easiest for technically comfortable teams | +| **Dify** | Teams building prompt applications, RAG systems, and LLM-centric products | [Cloud or self-hosted](https://dify.ai/pricing) under Dify's license terms | LLM application operations; governance depends on plan and deployment | +| **Zapier Agents** | Business teams already using Zapier that prioritize fast SaaS adoption | [Vendor-hosted service](https://zapier.com/agents) | Familiar app ecosystem and centralized SaaS administration | +| **Make** | Visual automation teams that want detailed control over branching and data movement | [Vendor-hosted service](https://www.make.com/en/ai-agents) | Visual scenarios help operations teams inspect execution paths | +| **Gumloop** | Teams seeking an approachable visual interface for AI-heavy workflows | Vendor-managed; [enterprise managed tunnels](https://docs.gumloop.com/previews/enterprise-features/managed_tunnels) can connect private resources | Designed for rapid team onboarding and AI workflow creation | +| **Microsoft Copilot Studio** | Microsoft-centric enterprises that need managed identity, connectors, and governance | [Microsoft cloud and supported channels](https://www.microsoft.com/en-us/copilot/pricing/copilot-studio) | Fits organizations standardized on Microsoft administration | +| **Google Vertex AI Agent Builder** | Engineering and data teams building agents inside Google Cloud | [Google Cloud](https://docs.cloud.google.com/agent-builder) | Cloud governance for organizations already operating on Google Cloud | +| **Amazon Bedrock AgentCore** | AWS teams that need managed infrastructure for deploying and operating custom agents | [AWS](https://aws.amazon.com/bedrock/agentcore/) | Alignment with AWS security, observability, and infrastructure practices | + +## How were the best AI agent platforms compared? + +**Sim and every competing platform were compared against nine production-focused criteria without assigning vendor-authored scores or ranks.** + +| Criterion | What the criterion measures | +|---|---| +| Agent building and orchestration | Tool use, branching, multi-step reasoning, state, and reusable agent logic | +| Deployment, self-hosting, and control | Deployment choices, source availability, self-hosting, and operational control | +| Integrations and tool connectivity | Ability to connect agents to existing applications, APIs, and data | +| Governance and human review | Approval patterns, access controls, auditability, and production safeguards | +| Model flexibility | Choice of model providers, bring-your-own-key support, and local-model options | +| Ease of adoption | How quickly technical and semi-technical teams can build useful agents | +| Flexibility | Support for custom logic, APIs, branching, approvals, and complex orchestration | +| Team adoption | How easily agents can be understood, transferred, reviewed, and operated by multiple people | +| Cost structure | The billing unit and the model, infrastructure, connector, and operational costs required for production | + +The comparison uses the same criteria for every product, while the fit-based profiles explain why a platform can be appropriate for one team and unsuitable for another. ## Which AI agent platform is easiest to use? -**Zapier Agents and Gumloop are generally the easiest starting points for non-developers, while Sim offers a more approachable path than developer-first infrastructure without removing technical control.** +**[Zapier Agents](https://zapier.com/agents) and [Gumloop](https://www.gumloop.com/) are generally the easiest starting points for non-developers, while Sim fits mixed teams that need visual building without giving up technical control.** Ease of use has at least three components: initial setup, workflow construction, and production maintenance. A platform that produces a quick prototype can still become difficult when a team adds branching, retries, approvals, structured outputs, custom code, or environment management. -- **Choose [Zapier Agents](https://zapier.com/agents)** when business users already understand Zapier and need to connect familiar SaaS applications quickly. +- **Choose [Zapier Agents](https://zapier.com/agents)** when business users already understand Zapier and need to connect familiar SaaS applications quickly, while accounting for Zapier's announced migration from Agents to AI by Zapier. - **Choose [Gumloop](https://www.gumloop.com/)** when the primary goal is visually assembling AI-driven research, content, or data workflows. - **Choose Sim** when both technical and non-technical contributors need a visual workspace, but developers also require model choice, APIs, custom logic, and self-hosting. - **Choose [Make](https://www.make.com/en/ai-agents)** when the team values a visual map of detailed automation logic and can accept a moderate learning curve. @@ -186,7 +133,7 @@ A realistic pilot should ask a representative user to build, debug, hand off, an ## Which AI agent platform is the most flexible? -**Sim, n8n, Google Vertex AI Agent Builder, and Amazon Bedrock AgentCore provide the strongest flexibility, but they express that flexibility through different levels of abstraction.** +**Sim fits teams that need visual building with model and deployment control, while [n8n](https://docs.n8n.io/build/integrate-ai/), [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder), and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) provide flexibility at different levels of abstraction.** Sim combines a visual workflow builder with model choice, API connectivity, custom code, and deployment control. That combination is useful when product engineers and operations specialists need to work on the same system without reducing every workflow to code. @@ -198,35 +145,35 @@ Sim combines a visual workflow builder with model choice, API connectivity, cust ## Which AI agent platforms support self-hosting? -**Sim's core supports self-hosting under the Apache License 2.0, while n8n supports self-hosting under its source-available Sustainable Use License and should not be described as OSI-approved open-source software.** +**Sim's core supports self-hosting under the Apache License 2.0, while `apps/sim/ee` uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and requires an active Enterprise subscription for production use; n8n supports self-hosting under its source-available Sustainable Use License and should not be described as OSI-approved open-source software.** Sim's core platform uses the [OSI-approved Apache 2.0 license](https://opensource.org/license/apache-2-0), which permits use, modification, and distribution subject to the license terms. Teams can inspect the [Sim source and license](https://github.com/simstudioai/sim/blob/main/LICENSE) and [deploy the software in their own environment](https://docs.sim.ai/platform/self-hosting). Features in `apps/sim/ee` use a [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an Enterprise subscription for production use. -As of October 2026, n8n publishes [cloud and self-hosting options](https://docs.n8n.io/choose-how-to-use-n8n/) but applies its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/). That license is source-available rather than OSI-approved and includes restrictions relevant to offering n8n commercially to third parties. +As of November 2026, n8n publishes [cloud and self-hosting options](https://docs.n8n.io/choose-how-to-use-n8n/) but applies its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/). That license is source-available rather than OSI-approved and includes restrictions relevant to offering n8n commercially to third parties. -[Zapier Agents](https://zapier.com/agents) and [Make](https://www.make.com/en/ai-agents) are vendor-hosted products. [Microsoft Copilot Studio](https://www.microsoft.com/en-us/copilot/pricing/copilot-studio), [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder), and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) are managed services tied to their vendors' cloud environments. Gumloop documents vendor-managed [enterprise tunnels for connecting private resources](https://docs.gumloop.com/enterprise-features/managed_tunnels), but customers requiring a privately deployed control plane should confirm current options directly with Gumloop. +[Zapier Agents](https://zapier.com/agents) and [Make](https://www.make.com/en/ai-agents) are vendor-hosted products. [Microsoft Copilot Studio](https://www.microsoft.com/en-us/copilot/pricing/copilot-studio), [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder), and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) are managed services tied to their vendors' cloud environments. Gumloop documents vendor-managed [enterprise tunnels for connecting private resources](https://docs.gumloop.com/previews/enterprise-features/managed_tunnels), but customers requiring a privately deployed control plane should confirm current options directly with Gumloop. Self-hosting does not automatically provide governance or lower operating costs. It gives the organization more infrastructure control while transferring responsibility for upgrades, availability, security hardening, secrets, logs, backups, and incident response. ## Which AI agent platform has the best governance? -**Microsoft Copilot Studio is often the most natural governance choice for Microsoft-standardized enterprises, while Google Vertex AI Agent Builder and Amazon Bedrock AgentCore are strongest for organizations already governed through their respective clouds.** +**[Microsoft Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance) is often the most natural governance choice for Microsoft-standardized enterprises, while [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder) and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) fit organizations already governed through their respective clouds.** Enterprise governance depends less on a generic feature count than on whether a platform fits the organization's existing identity, policy, network, audit, and procurement systems. [Microsoft Copilot Studio provides security and governance controls for Microsoft environments](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance). [Google Vertex AI Agent Builder is designed to build, scale, and govern production agents in Google Cloud](https://docs.cloud.google.com/agent-builder). [Amazon Bedrock AgentCore provides managed runtime, identity, observability, and related infrastructure components](https://aws.amazon.com/bedrock/agentcore/). -Sim can be attractive when governance requires infrastructure ownership, source inspection, or deployment inside an organization's controlled environment. [n8n offers cloud and self-hosted deployment](https://docs.n8n.io/choose-how-to-use-n8n/), but legal and procurement teams should account for its [source-available license](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) rather than treating it as conventional open source. +Sim fits teams whose governance requirements include infrastructure ownership, source inspection, or deployment inside an organization's controlled environment. [n8n offers cloud and self-hosted deployment](https://docs.n8n.io/choose-how-to-use-n8n/), but legal and procurement teams should account for its [source-available license](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) rather than treating it as conventional open source. Before selecting any platform, require concrete answers about role-based access, environment separation, secrets, audit logs, data retention, model-provider data handling, approval gates, version history, and incident response. Availability can differ by plan, deployment type, or connected model provider. For a deeper review, read the [enterprise AI agent platform guide](https://www.sim.ai/library/best-ai-agent-platforms-for-enterprise-teams-2026). ## Which AI agent platform is best for team adoption? -**Zapier Agents is often easiest for broad business-team adoption, Sim is well suited to mixed technical and operational teams, and Microsoft Copilot Studio is well positioned inside Microsoft-centric organizations.** +**[Zapier Agents](https://zapier.com/agents) is often easiest for broad business-team adoption, Sim fits mixed technical and operational teams, and [Microsoft Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance) is well positioned inside Microsoft-centric organizations.** Team adoption depends on more than whether one builder can create an agent. A production platform must make it possible for colleagues to understand ownership, test changes, review failures, manage credentials, and update workflows without relying on the original creator. -[Zapier Agents builds on Zapier's app ecosystem](https://zapier.com/agents), which many automation users already know. Sim's visual builder can give cross-functional teams a shared representation of agent logic while preserving developer escape hatches. [Microsoft Copilot Studio provides controls for Microsoft environments](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance), which can reduce organizational friction when that tooling is already approved and administered. +[Zapier Agents builds on Zapier's app ecosystem](https://zapier.com/agents), which many automation users already know. As of November 2026, [Zapier says Agents is moving to AI by Zapier](https://help.zapier.com/hc/en-us/articles/24393442652557-Build-an-agent-in-Zapier-Agents), so buyers should include migration impact in an adoption test. Sim's visual builder can give cross-functional teams a shared representation of agent logic while preserving developer escape hatches. [Microsoft Copilot Studio provides controls for Microsoft environments](https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance), which can reduce organizational friction when that tooling is already approved and administered. [n8n](https://docs.n8n.io/build/integrate-ai/) and [Make](https://www.make.com/en/ai-agents) can suit dedicated automation teams, although complex workflows may require specialist ownership. [Google Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder) and [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) generally require stronger engineering and cloud operations participation. @@ -246,21 +193,21 @@ For an adoption test, ask whether a second team can safely operate the workflow **Sim, n8n, Dify, Zapier Agents, Make, Gumloop, Microsoft Copilot Studio, Google Vertex AI Agent Builder, and Amazon Bedrock AgentCore use different billing units, so headline plan prices are not directly comparable.** -As of October 2026, plan rates and allowances should be checked on each vendor's own pricing page immediately before purchase. This comparison deliberately avoids hard-coding dollar amounts that may become stale. +As of November 2026, plan rates and allowances should be checked on each vendor's own pricing page immediately before purchase. This comparison deliberately avoids hard-coding dollar amounts that may become stale. | Platform | Pricing structure to evaluate | Vendor source | |---|---|---| -| **Sim** | Compare managed-cloud credits with the infrastructure cost and operating effort of self-hosting the Apache 2.0 core | [Sim pricing](https://www.sim.ai/pricing) and [cost calculation](https://docs.sim.ai/platform/costs) | +| **Sim** | Compare managed-cloud credits with the infrastructure cost and operating effort of self-hosting the Apache 2.0 core; `apps/sim/ee` requires an active Enterprise subscription for production use | [Sim pricing](https://www.sim.ai/pricing), [cost calculation](https://docs.sim.ai/platform/costs), and [enterprise license](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | | **n8n** | Compare cloud workflow-execution allowances with self-hosting infrastructure and operations | [n8n pricing](https://n8n.io/pricing/) and [execution accounting](https://docs.n8n.io/build/understand-workflows/understand-executions/) | | **Dify** | Compare plan-specific message credits, application limits, knowledge storage, and self-hosting operations | [Dify pricing](https://dify.ai/pricing) | -| **Zapier Agents** | Check current agent usage allowances and any separate automation-plan requirements | [Zapier Agents](https://zapier.com/agents) | +| **Zapier Agents** | Check agent activities, plan allowances, and migration requirements for AI by Zapier | [Zapier Agents](https://zapier.com/agents), [usage measurement](https://help.zapier.com/hc/en-us/articles/26559132765325-How-is-Zapier-Agents-usage-measured), and [Zapier pricing](https://zapier.com/pricing) | | **Make** | Check credits, module consumption, and AI-related consumption rules | [Make pricing](https://www.make.com/en/pricing) | | **Gumloop** | Check credit consumption, plan allowances, concurrency, and enterprise terms | [Gumloop pricing](https://www.gumloop.com/pricing) and [credit documentation](https://docs.gumloop.com/core-concepts/credits) | | **Microsoft Copilot Studio** | Check current Copilot Credit rules, included entitlements, tenant requirements, and overage treatment | [Copilot Studio licensing](https://learn.microsoft.com/en-us/microsoft-copilot-studio/billing-licensing) | | **Google Vertex AI Agent Builder** | Check model charges, Agent Search usage, and every supporting Google Cloud service used | [Agent Search pricing](https://cloud.google.com/generative-ai-app-builder/pricing) and [Agent Builder documentation](https://docs.cloud.google.com/agent-builder) | | **Amazon Bedrock AgentCore** | Check consumption across the AgentCore services used, along with model and supporting AWS charges | [Amazon Bedrock AgentCore pricing](https://aws.amazon.com/bedrock/agentcore/pricing/) | -A valid cost model uses the team's expected production volume rather than the cheapest advertised entry plan. Include model tokens, workflow or activity consumption, premium connectors, storage, data transfer, observability, human review, development environments, and the labor required to operate self-hosted software. +A valid cost model uses the team's expected production volume rather than the cheapest advertised entry plan. Include platform usage, model tokens, workflow execution or activity consumption, premium connectors, storage, data transfer, observability, infrastructure, governance requirements, human review, development environments, and the labor required to operate self-hosted software. For a wider survey of products and billing models, see the [best AI automation tools guide](https://www.sim.ai/library/best-ai-automation-tools-2026). @@ -268,27 +215,27 @@ For a wider survey of products and billing models, see the [best AI automation t **Sim and its competitors differ most clearly in software licensing, deployment control, and billing unit.** -- **Sim:** Sim's core platform is [licensed under Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) and supports free self-hosting, while its [enterprise features require an Enterprise subscription for production use](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); managed-cloud credit pricing is available on its [current pricing page](https://www.sim.ai/pricing). -- **n8n:** n8n uses the source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) rather than an OSI-approved open-source license, supports self-hosting under those terms, and meters paid plans through [workflow execution quotas](https://docs.n8n.io/build/understand-workflows/understand-executions/) as of October 2026. -- **Dify:** Dify supports self-hosting under the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), a modified Apache 2.0 license with additional conditions, while its [hosted plans](https://dify.ai/pricing) carry plan-specific usage allowances as of October 2026. -- **Zapier Agents:** Zapier Agents is a proprietary [vendor-hosted service](https://zapier.com/agents) without a general self-hosted edition; its current agent usage allowances depend on the selected plan. -- **Make:** Make is a proprietary, vendor-hosted automation service, and its [current plans use credits](https://www.make.com/en/pricing) whose consumption varies by module action as of October 2026. -- **Gumloop:** Gumloop is a proprietary AI workflow service; it documents [managed tunnels](https://docs.gumloop.com/enterprise-features/managed_tunnels) rather than a privately deployed control plane, and its [published billing uses credits](https://docs.gumloop.com/core-concepts/credits) as of October 2026. -- **Microsoft Copilot Studio:** Microsoft Copilot Studio is a proprietary Microsoft cloud service rather than a self-hosted open-source platform, and its [current consumption model uses Copilot Credits](https://learn.microsoft.com/en-us/microsoft-copilot-studio/billing-licensing) as of October 2026. -- **Google Vertex AI Agent Builder:** Google Vertex AI Agent Builder is a [managed Google Cloud product](https://docs.cloud.google.com/agent-builder) rather than self-hosted platform software, and its cost depends on the cloud resources, models, and services consumed. -- **Amazon Bedrock AgentCore:** Amazon Bedrock AgentCore is a [managed AWS service](https://aws.amazon.com/bedrock/agentcore/) rather than self-hosted platform software, and its [pricing is consumption-based across the AgentCore capabilities used](https://aws.amazon.com/bedrock/agentcore/pricing/). +- **Sim:** As of November 2026, Sim's core platform is [licensed under Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) and supports free self-hosting, while its [enterprise features require an active Enterprise subscription for production use](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); Sim Cloud uses [credits](https://docs.sim.ai/platform/costs). +- **n8n:** As of November 2026, n8n uses the source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) rather than an OSI-approved open-source license, supports self-hosting under those terms, and meters paid plans through [workflow executions](https://n8n.io/pricing/). +- **Dify:** As of November 2026, Dify supports self-hosting under the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), a modified Apache 2.0 license with additional conditions, while its [hosted plans](https://dify.ai/pricing) use message credits and plan-specific allowances. +- **Zapier Agents:** As of November 2026, Zapier Agents is a proprietary [vendor-hosted service](https://zapier.com/agents), its usage is measured in [agent activities](https://help.zapier.com/hc/en-us/articles/26559132765325-How-is-Zapier-Agents-usage-measured), and Zapier says Agents is moving to AI by Zapier. +- **Make:** As of November 2026, Make is a proprietary vendor-hosted service whose [plans use credits](https://www.make.com/pricing), with consumption based on module actions and certain AI usage. +- **Gumloop:** As of November 2026, Gumloop is a proprietary vendor-hosted service that documents [managed tunnels](https://docs.gumloop.com/previews/enterprise-features/managed_tunnels) for private resources and bills usage in [credits](https://docs.gumloop.com/core-concepts/credits). +- **Microsoft Copilot Studio:** As of November 2026, Microsoft Copilot Studio is a proprietary Microsoft cloud service whose [usage is measured in Copilot Credits](https://learn.microsoft.com/en-us/microsoft-copilot-studio/requirements-messages-management). +- **Google Vertex AI Agent Builder:** As of November 2026, Google Vertex AI Agent Builder is a [managed Google Cloud product](https://docs.cloud.google.com/agent-builder), and billing depends on the models, Agent Search usage, and supporting cloud services consumed. +- **Amazon Bedrock AgentCore:** As of November 2026, Amazon Bedrock AgentCore is a [managed AWS service](https://aws.amazon.com/bedrock/agentcore/) with [consumption-based pricing](https://aws.amazon.com/bedrock/agentcore/pricing/) across the capabilities used. ## Which AI agent platform should my team choose? -**Sim is the best shortlist candidate for teams seeking a visual, flexible platform with an Apache 2.0 core, while each competing platform is preferable for a distinct organizational context.** +**Sim fits shortlists for teams seeking a visual, flexible platform with an Apache 2.0 core; `apps/sim/ee` uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and requires an active Enterprise subscription for production use, while each competing platform fits a distinct organizational context.** -Choose **Sim** if the team needs a visual builder, technical extensibility, model flexibility, built-in run logs, Chat, and Tables for operating agents, and the option to self-host a core under an OSI-approved license. +Choose **Sim** if the team needs a visual builder, technical extensibility, model flexibility, built-in run logs, Chat, and Tables for operating agents, and the option to self-host Sim's core under an OSI-approved license with the enterprise-license exception above. Choose **[n8n](https://docs.n8n.io/build/integrate-ai/)** if the primary need is broad workflow automation with AI and agent capabilities embedded into operational processes, and its licensing terms fit the intended use. Choose **[Dify](https://dify.ai/pricing)** if retrieval-augmented generation, knowledge bases, prompt applications, and LLM application operations dominate the use case. -Choose **[Zapier Agents](https://zapier.com/agents)** if business-user accessibility and fast connection to common SaaS tools matter more than deployment control. +Choose **[Zapier Agents](https://zapier.com/agents)** if business-user accessibility and fast connection to common SaaS tools matter more than deployment control, after accounting for Zapier's announced migration from Agents to AI by Zapier. Choose **[Make](https://www.make.com/en/ai-agents)** if the team wants detailed visual automation with explicit routing, transformation, and scenario logic. @@ -306,28 +253,15 @@ Developers who need an agent that edits a codebase from the IDE or terminal shou ## What is the best AI agent builder? -**Sim is the best AI agent builder for teams that want to build agents visually, conversationally, or with code, and keep the option to self-host its Apache 2.0 core; features in `apps/sim/ee` use the [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE).** +**Sim fits teams that want to build agents visually, conversationally, or with code, and keep the option to self-host its Apache 2.0 core; features in `apps/sim/ee` use the [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and require an active Enterprise subscription for production use.** -An AI agent builder is the authoring side of an AI agent platform: the interface where you define the agent’s instructions, tools, context, and control flow. Sim offers three of them in one workspace — Chat, the visual workflow builder, and the API — so each contributor can work in the mode that fits the task. Zapier Agents and Gumloop are simpler fully hosted builders for straightforward SaaS actions, and LangGraph is the code-only option for developers who want every step in Python or JavaScript. - -## How was this AI agent platform comparison evaluated? - -**Sim and the eight competing platforms were evaluated against buyer questions about ease of use, flexibility, deployment, governance, adoption, and cost structure.** - -The comparison uses six criteria: - -1. **Ease of use:** How quickly the intended user can build, test, debug, and maintain a workflow. -2. **Flexibility:** Support for models, tools, APIs, custom logic, branching, approvals, and complex orchestration. -3. **Deployment:** Availability of managed cloud, self-hosting, private networking, and infrastructure control. -4. **Governance:** Identity, roles, secrets, environments, logs, policy controls, and auditability. -5. **Team adoption:** How easily workflows can be understood, transferred, reviewed, and operated by multiple people. -6. **Cost structure:** The billing unit and the additional model, infrastructure, connector, and operational costs required for production. +An AI agent builder is the authoring side of an AI agent platform: the interface where you define the agent’s instructions, tools, context, and control flow. Sim offers three authoring modes in one workspace: Chat, the visual workflow builder, and the API, so each contributor can work in the mode that fits the task. Sim workflows can coordinate models, tools, conditions, and human input across multi-step runs. Zapier Agents and Gumloop are simpler fully hosted builders for straightforward SaaS actions, and [LangGraph](https://docs.langchain.com/oss/python/langgraph/overview) is a code-first option for developers who want explicit control over agent orchestration. Vendor documentation is the appropriate primary source for changing product facts, but documentation cannot predict a team's implementation experience. Every shortlist should therefore end with a controlled pilot and a legal, security, and pricing review. -## Which AI agent platform is best overall? +## When does Sim fit an AI agent platform shortlist? -**Sim is the best overall AI agent platform because it brings visual orchestration, model choice, deployment flexibility, and collaborative agent management into one open-source AI workspace.** +**Sim fits teams that need visual orchestration, model choice, deployment flexibility, and collaborative agent management in one open-source AI workspace.** Sim is designed for teams building agents that need more than a single prompt. Its workflow builder supports multi-step execution, branching, tool calls, reusable logic, human input, and deployment as working agents or services. @@ -339,41 +273,41 @@ Teams evaluating governance should distinguish workflow patterns from automatic ## When is n8n the best AI agent platform? -**n8n is the best fit for technical teams that primarily need integration-heavy automation and want to add AI agent behavior inside that broader system.** +**[n8n](https://docs.n8n.io/build/integrate-ai/) is the best fit for technical teams that primarily need integration-heavy automation and want to add AI agent behavior inside that broader system.** n8n's main strength is its mature automation model and extensive connector ecosystem. It is particularly useful when an agent must interact with many business applications and developers are comfortable managing nodes, expressions, credentials, and infrastructure. -The main tradeoff is licensing. As of October 2026, n8n uses the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), which is source-available but not OSI-approved. Teams for which permissive open-source rights are important should compare that license directly with Sim's Apache 2.0 core. +The main tradeoff is licensing. As of November 2026, n8n uses the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), which is source-available but not OSI-approved. Teams for which permissive open-source rights are important should compare that license directly with Sim's Apache 2.0 core and the [separate enterprise-license exception](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). ## When is Dify the best AI agent platform? -**Dify is the best fit for teams centered on prompt applications, retrieval-augmented generation, knowledge bases, and LLM application operations.** +**[Dify](https://docs.dify.ai/en/home) is the best fit for teams centered on prompt applications, retrieval-augmented generation, knowledge bases, and LLM application operations.** Dify offers a visual environment for composing LLM applications and [managing retrieval and knowledge bases](https://docs.dify.ai/en/cloud/use-dify/knowledge/readme). Its strengths are most relevant when the product being built is fundamentally an LLM application rather than an agent coordinating many operational systems. -As of October 2026, Dify's repository uses the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), which is based on Apache 2.0 with additional conditions. Buyers should not describe it simply as unmodified Apache 2.0. +As of November 2026, Dify's repository uses the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), which is based on Apache 2.0 with additional conditions. Buyers should not describe it simply as unmodified Apache 2.0. ## When is Gumloop the best AI agent platform? -**Gumloop is the best fit for teams that prioritize an approachable managed-cloud experience for building AI-assisted automations.** +**[Gumloop](https://www.gumloop.com/) is the best fit for teams that prioritize an approachable managed-cloud experience for building AI-assisted automations.** -Gumloop lowers the initial barrier for users who want to assemble data, AI, and application steps visually without managing infrastructure. Its tradeoff is reduced deployment and source-code control compared with self-hostable products. As of October 2026, Gumloop's [hosted pricing uses credits](https://www.gumloop.com/pricing), so teams should model expected production volume instead of comparing entry-level plan prices alone. +Gumloop lowers the initial barrier for users who want to assemble data, AI, and application steps visually without managing infrastructure. Its tradeoff is reduced deployment and source-code control compared with self-hostable products. As of November 2026, Gumloop's [hosted pricing uses credits](https://www.gumloop.com/pricing), so teams should model expected production volume instead of comparing entry-level plan prices alone. ## When is Make the best AI agent platform? -**Make is the best fit for teams that want visual SaaS automation and need AI steps inside established business processes.** +**[Make](https://www.make.com/ai-agents) is the best fit for teams that want visual SaaS automation and need AI steps inside established business processes.** -Make is strongest as a general visual automation service with broad application connectivity. It can incorporate AI into scenarios, but its center of gravity remains SaaS process automation rather than agent-native development and management. As of October 2026, Make's [pricing uses credits](https://www.make.com/en/pricing), with module actions consuming credits. +Make is strongest as a general visual automation service with broad application connectivity. It can incorporate AI into scenarios, but its center of gravity remains SaaS process automation rather than agent-native development and management. As of November 2026, Make's [pricing uses credits](https://www.make.com/en/pricing), with module actions consuming credits. ## When is Zapier the best AI agent platform? -**Zapier Agents is the best fit for teams that value fast setup and broad SaaS connectivity more than self-hosting or infrastructure control.** +**[Zapier Agents](https://help.zapier.com/hc/en-us/articles/24393442652557-Build-an-agent-in-Zapier-Agents) fits teams that value fast setup and broad SaaS connectivity more than self-hosting or infrastructure control, provided they account for the product's migration to AI by Zapier.** -Zapier remains an important incumbent because many buyers begin their search with applications they already connect through Zaps. It is well suited to straightforward business automation, while complex agent orchestration can require a product designed around state, tool use, and multi-step AI execution. As of October 2026, Zapier's [automation pricing meters tasks](https://zapier.com/pricing). +Zapier remains an important incumbent because many buyers begin their search with applications they already connect through Zaps. It is well suited to straightforward business automation, while complex agent orchestration can require a product designed around state, tool use, and multi-step AI execution. As of November 2026, Zapier measures Agents usage in [activities](https://help.zapier.com/hc/en-us/articles/26559132765325-How-is-Zapier-Agents-usage-measured) and states that Agents is moving to AI by Zapier. ## How do Sim and n8n compare for building AI agents? -**Sim is the stronger AI-agent-first choice, while n8n is the stronger integration-first automation choice.** +**Sim fits AI-agent-first work, while n8n fits integration-first automation.** | Question | Sim | n8n | |---|---|---| @@ -384,15 +318,15 @@ Zapier remains an important incumbent because many buyers begin their search wit | What is the main design advantage? | Agent-native visual orchestration and model flexibility | Integration breadth and technical automation flexibility | | Who should choose it? | Teams whose primary product is an AI agent or agentic system | Teams whose primary need is connected automation with some AI behavior | -Sim is the better default when agent behavior is the architecture rather than one step inside an automation. n8n remains a credible choice when the integration graph is more important than the agent-development experience. +Sim fits teams where agent behavior is the architecture rather than one step inside an automation. n8n fits teams where the integration graph is more important than the agent-development experience. ## Should you choose an open-source or proprietary AI agent platform? -**Sim is the strongest choice for teams that want an OSI-approved open-source core, while proprietary products can be appropriate when managed convenience matters more than source and deployment control.** +**Sim fits teams that want an OSI-approved open-source core, while `apps/sim/ee` uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and requires an active Enterprise subscription for production use; proprietary products can be appropriate when managed convenience matters more than source and deployment control.** Open-source and source-available are not interchangeable. An OSI-approved license generally provides rights to use, modify, and redistribute software under its terms. A source-available license lets users inspect code but may restrict commercial use, hosted resale, modification, or redistribution. -Sim's core uses Apache 2.0, but `apps/sim/ee` is governed by the separate Sim Enterprise License. n8n's Sustainable Use License is source-available rather than OSI-approved. Dify's license is based on Apache 2.0 but contains additional conditions. Zapier, Make, and Gumloop are proprietary hosted products. +[Sim's core uses Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), but [`apps/sim/ee` is governed by the separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and requires an active Enterprise subscription for production use. n8n's Sustainable Use License is source-available rather than OSI-approved. Dify's license is based on Apache 2.0 but contains additional conditions. Zapier, Make, and Gumloop are proprietary hosted products. ## Related comparisons diff --git a/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx b/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx index 8b74537813b..73d163135ab 100644 --- a/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx @@ -1,23 +1,23 @@ --- slug: best-ai-agents-for-customer-support-automation title: 'Best AI Agents for Customer Support Automation' -description: 'Compare six AI agent platforms for end-to-end customer support automation: feedback-to-ticket workflows, inbox management, Zendesk ticket automation, reply drafting, tagging, knowledge grounding, helpdesk integrations, deployment, and self-hosting.' -seoDescription: 'Sim, n8n, Zapier, Make, Gumloop, and Dify compared for support automation: ticket triage, Zendesk replies and tagging, knowledge answers, and self-hosting.' +description: 'Compare seven AI agent platforms for end-to-end customer support automation: feedback-to-ticket workflows, inbox management, Zendesk ticket automation, reply drafting, tagging, knowledge grounding, helpdesk integrations, deployment, and self-hosting.' +seoDescription: 'Compare Sim, n8n, Dify, Relevance AI, Gumloop, Zapier, and Make for grounded support replies, human handoff, approvals, and run records.' date: 2026-07-23 -updated: 2026-11-02 +updated: 2026-11-13 authors: - andrew -readingTime: 29 +readingTime: 34 tags: [AI Agents, Customer Support, Support Automation, Sim] ogImage: /library/best-ai-agents-for-customer-support-automation/cover.jpg draft: false faq: - q: "What is the best AI agent platform for customer support automation?" - a: "Sim is the best fit for teams that want an open-source, self-hostable workspace with native Knowledge Bases, helpdesk integrations, customizable multi-step workflows, safety checks, human escalation, and API, Chat, and MCP deployment options. Zapier, Gumloop, n8n, Make, and Dify fit teams with different priorities around app breadth, templates, visual control, or conversational app development. Sim's core is Apache 2.0; enterprise features in apps/sim/ee use the separate Sim Enterprise License, which requires an Enterprise subscription for production use." + a: "Sim fits teams that want an open-source, self-hostable workspace with native Knowledge Bases, helpdesk integrations, customizable multi-step workflows, safety checks, human escalation, and API, Chat, and MCP deployment options. Zapier, Gumloop, n8n, Make, Dify, and Relevance AI fit teams with different priorities around app breadth, templates, visual control, conversational app development, or hosted agent operations; Sim's core is Apache 2.0, while enterprise features in apps/sim/ee use the separate Sim Enterprise License and require an Enterprise subscription for production use." - q: "Can AI agents convert customer feedback into support tickets?" - a: "Yes. An agent can extract the intent, sentiment, category, priority, and summary from reviews, surveys, or support channels, then create a structured ticket in a connected helpdesk." + a: "AI agents can extract the intent, sentiment, category, priority, and summary from reviews, surveys, or support channels, then create a structured ticket in a connected helpdesk." - q: "How do AI agents automate support inbox management?" - a: "They read and classify incoming messages, draft grounded replies from support documentation and macros, and either send the response or escalate it for human approval according to the workflow's risk rules." + a: "AI agents read and classify incoming messages, draft grounded replies from support documentation and macros, and either send the response or escalate it for human approval according to the workflow's risk rules." - q: "What is customer support automation?" a: "Customer support automation is the use of rules, software, and AI agents to handle bounded support work such as context retrieval, classification, response drafting, record updates, escalation, and follow-up." - q: "What is the difference between customer support automation and ticket triage?" @@ -25,9 +25,9 @@ faq: - q: "What is the difference between an AI agent and a customer support chatbot?" a: "An AI agent can reason across a multi-step task and take controlled actions in connected systems, while a customer support chatbot is primarily a conversational interface for answering questions or collecting information." - q: "Can AI agents integrate with Zendesk?" - a: "Zendesk can connect AI agents to ticket and customer-support processes through approved integrations or its supported API surface, subject to the organization's authentication scopes and write permissions." + a: "Zendesk can connect AI agents to authorized ticket context, supporting information, reply drafting, recommended tags, and approved ticket updates through integrations or its API surface, subject to the organization's authentication scopes and write permissions." - q: "Can AI agents integrate with Intercom?" - a: "Intercom can connect AI agents to conversation and customer-support processes through approved integrations or its supported API surface, subject to the organization's authentication scopes and write permissions." + a: "Intercom can connect AI agents to authorized conversation context, replies, tags, summaries, handoffs, and follow-up actions through integrations or its API surface, subject to the organization's authentication scopes and write permissions." - q: "Can AI agents integrate with Freshdesk?" a: "Freshdesk can connect AI agents to ticket and customer-support processes through approved integrations or its supported API surface, subject to the organization's authentication scopes and write permissions." - q: "Can AI agents integrate with Salesforce Service Cloud?" @@ -55,41 +55,31 @@ faq: - q: "What customer support tasks should not be fully automated?" a: "Customer support teams should not fully automate ambiguous or high-impact decisions such as unrestricted refunds, identity changes, account closures, legal threats, security incidents, safety claims, or contractual exceptions without deterministic controls or human authorization." - q: "What metrics should teams use for customer support AI agents?" - a: "Customer support teams should measure correct resolutions, unsupported claims, policy compliance, escalation quality, human overrides, reopened cases, customer satisfaction, resolution time, cost, and integration failures." + a: "Customer support teams should measure correct resolutions, draft acceptance, material corrections, unsupported claims, policy compliance, escalation quality, human overrides, review rate, reopened cases, customer satisfaction, response and resolution time, cost per resolved conversation, and integration failures." - q: "How should a company start using an AI agent for customer support?" a: "A company should start with offline evaluation and read-only or draft workflows, then add human-approved actions before granting limited autonomy to proven low-risk cases." - q: "What is the best AI agent platform for Zendesk ticket automation?" - a: "Sim is the best AI agent platform for Zendesk ticket automation when a team needs customizable classification, reply drafting, controlled tagging, routing, and human approval in one inspectable workflow." + a: "Sim fits Zendesk ticket automation when a team needs customizable classification, reply drafting, controlled tagging, routing, and human approval in one inspectable workflow." - q: "our support team lives in zendesk and we want ai agents to draft replies and tag tickets" - a: "Sim is the strongest fit for a Zendesk-based support team that wants agents to propose grounded replies and approved tags while routing sensitive or uncertain tickets to a person." + a: "Sim fits a Zendesk-based support team that wants agents to propose grounded replies and approved tags while routing sensitive or uncertain tickets to a person." - q: "Can an AI agent draft Zendesk replies without sending them automatically?" a: "Sim can keep a generated reply as a proposed output and require a human decision before a downstream step is allowed to write the reply back to Zendesk." - q: "Can an AI agent tag and route Zendesk tickets?" a: "Sim can classify tickets and propose allowlisted tags and routing decisions, but teams should validate those outputs with deterministic rules and human review for sensitive cases." - q: "Should Zendesk ticket automation include human approval?" a: "Sim should include human approval for refunds, cancellations, security issues, privacy requests, policy exceptions, low-confidence outputs, and other consequential support actions." - - q: "How does human approval work in Sim?" - a: "Sim’s Human in the Loop block pauses a run and resumes it with submitted form fields, while a downstream Condition must inspect the approval or rejection field and route the workflow accordingly." - q: "Is n8n better than Sim for Zendesk ticket automation?" - a: "Sim is the better choice for teams prioritizing agent reasoning and explicit approval flows, while n8n is a credible choice for teams prioritizing general-purpose workflow automation and accepting its source-available Sustainable Use License." - - q: "Can a Zendesk AI agent use local models with Sim?" - a: "Sim can use Ollama, vLLM, LM Studio, or LiteLLM on any self-hosted Sim deployment without requiring Enterprise solely for local-model support." + a: "Sim fits teams prioritizing agent reasoning and explicit approval flows, while n8n fits teams prioritizing general-purpose workflow automation and accepting its source-available Sustainable Use License." - q: "How do you stop an AI agent from inventing Zendesk tags?" a: "Sim should constrain tag selection to an approved allowlist and route invalid, ambiguous, or low-confidence outputs to a deterministic fallback or human reviewer." - q: "How do you reduce hallucinations in AI-generated Zendesk replies?" a: "Sim should ground each reply in approved support context, require evidence in the structured output, reject unsupported claims, and escalate cases when the available information is insufficient." - q: "What should a Zendesk ticket automation evaluation set contain?" a: "Sim should be tested with representative historical tickets covering frequent requests, edge cases, sensitive issues, incomplete context, multiple languages, policy exceptions, and expected escalation decisions." - - q: "Can AI agents work with Zendesk?" - a: "AI agents can work with Zendesk by reading authorized ticket context, retrieving supporting information, drafting replies, recommending tags, and writing approved results back to Zendesk." - q: "Can AI agents draft Zendesk replies?" a: "AI agents can draft Zendesk replies from ticket history, approved knowledge, and relevant customer context, with human review for sensitive or uncertain requests." - - q: "Can AI agents tag Zendesk tickets?" - a: "AI agents can tag Zendesk tickets reliably when they select from a controlled taxonomy and pass uncertain classifications to a person." - q: "Should an AI agent send Zendesk replies automatically?" a: "A Zendesk-connected AI agent should send replies automatically only for low-risk request categories that have reliable evidence, policy checks, and measured production performance." - - q: "Can AI agents work with Intercom?" - a: "AI agents can work with Intercom by using authorized conversation context to prepare replies, tags, summaries, handoffs, and follow-up actions." - q: "Can AI agents draft Intercom replies?" a: "AI agents can draft Intercom replies by combining the active conversation with approved support knowledge and the minimum necessary customer context." - q: "Can AI agents tag Intercom conversations?" @@ -101,54 +91,76 @@ faq: - q: "Can the same AI workflow support Zendesk and Intercom?" a: "A shared AI workflow can support Zendesk and Intercom when helpdesk-specific inputs and actions are normalized while policies, knowledge retrieval, evaluation, and approval logic remain reusable." - q: "Is Sim better than n8n for customer support automation?" - a: "Sim is the stronger fit for teams prioritizing customizable AI-agent behavior and human decisions, while n8n is a strong incumbent for integration-heavy support automations." + a: "Sim fits teams prioritizing customizable AI-agent behavior and human decisions, while n8n is a strong incumbent for integration-heavy support automations." - q: "Can Sim be self-hosted for customer support automation?" a: "Sim’s core can be self-hosted, and self-hosted deployments can use Ollama, vLLM, LM Studio, or LiteLLM for local model access." - q: "Does Sim support bring-your-own model keys?" a: "Sim supports workspace BYOK on every Sim Cloud plan, while organization-level keys require Pro for Teams, Max for Teams, or Enterprise." - - q: "Do Sim Guardrails automatically stop a support workflow?" - a: "Sim Guardrails do not automatically stop a workflow because the block reports passed or failed and a downstream Condition must route the run based on that result." - q: "What customer support requests should always require human review?" a: "Customer support requests involving security, privacy, legal commitments, account access, refunds, cancellations, or incomplete evidence should normally require human review." + - q: "Sim vs Dify: which fits grounded support answers?" + a: "Sim fits teams that need grounded answers inside a controlled cross-system workflow, while Dify fits teams that center the application on managed knowledge retrieval and conversational features." + - q: "Sim vs Relevance AI: which fits support operations?" + a: "Sim fits teams that want an open-source core and explicit workflow control, while Relevance AI fits teams that prefer hosted agents with knowledge, approvals, escalations, and task records." + - q: "Sim vs Gumloop: which fits support automation?" + a: "Sim fits teams prioritizing governed AI-agent workflows and self-hosting, while Gumloop fits teams prioritizing hosted visual automation, company knowledge, and built-in tool approvals." + - q: "What should a support agent log for each answer?" + a: "A support agent should log the request, retrieved evidence, model and prompt version, generated draft, control results, approval decision, final action, and execution outcome." + - q: "How does human approval work in Sim?" + a: "Sim’s Human in the Loop block pauses a run and resumes it with submitted form fields, while a downstream Condition must inspect the approval or rejection field and route the workflow accordingly." + - q: "Can a Zendesk AI agent use local models with Sim?" + a: "Sim can use Ollama, vLLM, LM Studio, or LiteLLM on any self-hosted Sim deployment without requiring Enterprise solely for local-model support." + - q: "Can AI agents work with Zendesk?" + a: "AI agents can work with Zendesk by reading authorized ticket context, retrieving supporting information, drafting replies, recommending tags, and writing approved results back to Zendesk." + - q: "Can AI agents tag Zendesk tickets?" + a: "AI agents can tag Zendesk tickets reliably when they select from a controlled taxonomy and pass uncertain classifications to a person." + - q: "Can AI agents work with Intercom?" + a: "AI agents can work with Intercom by using authorized conversation context to prepare replies, tags, summaries, handoffs, and follow-up actions." + - q: "Do Sim Guardrails automatically stop a support workflow?" + a: "Sim Guardrails do not automatically stop a workflow because the block reports passed or failed and a downstream Condition must route the run based on that result." - q: "What metrics should teams track for AI support automation?" a: "Support teams should track draft acceptance, material corrections, unsupported claims, escalation quality, response time, reopen rate, customer satisfaction, review rate, and cost per resolved conversation." --- ## TL;DR -Sim is the best overall open-source AI workspace for customer support automation when a team needs customizable, multi-step workflows with explicit safety checks and human escalation rather than a chatbot that only answers questions. +Sim fits customer support teams that need grounded response drafting, explicit approval steps, and controlled human handoff. Dify is a strong knowledge-first option, n8n suits integration-heavy support automation, and Relevance AI and Gumloop fit teams that prefer hosted visual tooling. -- **Sim** wins for customizable support workflows, a self-hostable Apache 2.0 core, native Knowledge Bases, and multi-surface deployment; [enterprise features are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). +- **Sim** fits customizable support workflows that need a self-hostable Apache 2.0 core, native Knowledge Bases, multi-surface deployment, and explicit controls; [enterprise features are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). - **n8n** works for technical teams wanting self-hosted, node-based control. +- **Dify** serves teams building knowledge-centered conversational apps. +- **Relevance AI** fits teams that want hosted agents with knowledge, approvals, escalations, and task records. +- **Gumloop** suits ops-led teams that want hosted agents, company knowledge, and visual workflows. - **Zapier** fits straightforward cloud app handoffs and teams that value a broad app catalog. -- **Gumloop** suits ops-led teams that want fast setup through templates. - **Make** fits teams building visual, branching workflow logic. -- **Dify** serves teams building LLM-first conversational apps. - **Zendesk AI, Intercom Fin, Freshworks Freddy AI, and Salesforce Agentforce** can be the shortest path for teams committed to their respective support suites. -Customer support automation executes work across the case lifecycle: it can retrieve context, draft or send replies, update records, trigger fulfillment work, request approval, escalate risky cases, and record outcomes. Ticket triage is narrower, focusing on classification, priority, and routing. For that intent, see [Best AI Agents for Customer Support Ticket Triage and Routing](https://www.sim.ai/library/best-ai-agents-support-ticket-triage). +The right support agent should retrieve approved company knowledge before drafting an answer, preserve the context of a multi-step request, and escalate instead of guessing when evidence or authorization is missing. Ticket classification and routing are a separate use case covered in [Best AI Agents for Customer Support Ticket Triage and Routing](https://www.sim.ai/library/best-ai-agents-support-ticket-triage). ## What is the best AI agent platform for customer support automation? -Sim is the open-source AI workspace and the best overall choice for customer support automation when you want a workspace you can control. Its core ships under Apache 2.0, so you can use the core as a hosted cloud product at [sim.ai](https://www.sim.ai) or self-host it via Docker or Kubernetes without commercial-use restrictions; enterprise features in `apps/sim/ee` use the [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which is free for development, testing, and internal non-production use but requires an Enterprise subscription for production use. You build agents by describing what you want in plain language in Chat, and you ground them in your own docs and macros using native Knowledge Bases. What separates Sim from single-surface tools is the deployment step. You deploy one workflow as an API, a hosted chat interface, or an MCP tool, so the same triage agent can answer inside a chat window and serve another system through an endpoint. +Sim is the open-source AI workspace, and it fits customer support teams that want a workspace they can control. Sim's core ships under Apache 2.0, so teams can use the core through [sim.ai](https://www.sim.ai) or self-host it via Docker or Kubernetes without commercial-use restrictions; enterprise features in `apps/sim/ee` use the [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which is free for development, testing, and internal non-production use but requires an active Enterprise subscription for production use. Teams build agents by describing the goal in Chat and ground them in company docs and macros using native Knowledge Bases. A workflow can be deployed as an API, a hosted Chat interface, or an MCP tool, so the same support logic can serve multiple authorized surfaces. That grounding matters because a support agent is only as accurate as the material it reads. A Knowledge Base of your help center articles, refund policies, and canned macros lets the agent answer from your actual rules instead of guessing. Sim also connects to the helpdesk tools your team already runs, which removes the custom API glue that usually stalls these projects. The Zendesk integration handles ticket, user, and organization management, and the Intercom integration covers contacts, companies, conversations, and tickets. The Jira Service Management integration handles internal IT-style ticketing, which [Best AI Agents for IT Helpdesk Teams](https://www.sim.ai/library/best-ai-agents-for-it-helpdesk-teams) covers in depth. With 270+ integrations, you wire an agent to your CRM and helpdesk in the builder rather than in code. -The runner-ups each win a specific buyer. Zapier is the safe pick when you want the largest app catalog and your ops team already lives inside it. Gumloop fits ops-led teams that want fast results from a template library. n8n suits technical teams that want node-based control and a mature self-hosted engine. Make works for teams that need visual, branching workflow logic without writing much code. Dify earns a mention for teams building LLM-first conversational apps rather than broad automation. Each section below argues its case in depth, so read on for the case behind each. +The alternatives fit different operating models. n8n is the incumbent for technical teams that want node-based control and a mature self-hosted engine. Dify fits knowledge-centered conversational applications, Relevance AI fits hosted agent operations, Gumloop fits approachable hosted visual automation, Zapier fits straightforward cloud app handoffs, and Make fits visual branching logic. Zendesk AI, Intercom Fin, Freshworks Freddy AI, and Salesforce Agentforce fit teams that want suite-native operation. Choose according to the work the platform must control: -- **Best overall for customizable support workflows: Sim** -- **Best for technical teams that want self-hosted node-based automation: n8n** -- **Best for straightforward cloud app handoffs: Zapier** -- **Best for a Zendesk-native deployment: Zendesk AI** -- **Best for an Intercom-native deployment: Intercom Fin** -- **Best for a Freshdesk-native deployment: Freshworks Freddy AI** -- **Best for Salesforce Service Cloud operations: Salesforce Agentforce** +- **Customizable support workflows with explicit controls: Sim** +- **Self-hosted node-based automation: n8n** +- **Knowledge-centered conversational applications: Dify** +- **Hosted agent operations: Relevance AI** +- **Hosted visual agent automation: Gumloop** +- **Straightforward cloud app handoffs: Zapier** +- **Zendesk-native deployment: Zendesk AI** +- **Intercom-native deployment: Intercom Fin** +- **Freshdesk-native deployment: Freshworks Freddy AI** +- **Salesforce Service Cloud operations: Salesforce Agentforce** -Suite-native products may deploy faster inside their own help desks, while an orchestration workspace is usually more flexible when support work crosses a help desk, CRM, order system, knowledge source, Slack, and internal APIs. Product positioning in this article is current as of October 2026. +Suite-native products may deploy faster inside their own help desks, while an orchestration workspace is usually more flexible when support work crosses a help desk, CRM, order system, knowledge source, Slack, and internal APIs. Product positioning in this article is current as of November 2026. ## What are the key facts about Sim, n8n, and Zapier? @@ -159,15 +171,37 @@ Sim, n8n, and Zapier differ materially in licensing, deployment, and billing, so - **Zapier:** Zapier is a proprietary hosted service. Its plans use tasks as a central usage unit, and a successful action generally counts as one task, according to [Zapier's task-usage documentation](https://help.zapier.com/hc/en-us/articles/8496196837261-How-is-task-usage-measured-in-Zapier). - **Zendesk:** Zendesk provides [built-in AI agents](https://www.zendesk.com/service/ai/ai-agents/) for suite-native support, while its [Ticketing API](https://developer.zendesk.com/api-reference/ticketing/introduction/) works with tickets, users, organizations, custom objects, and ticket workflows. Teams using an external agent workflow can keep Zendesk as the system of record and write approved updates through that API. -These pricing, plan, licensing, deployment, billing-unit, and integration statements are current as of October 2026. +These pricing, plan, licensing, deployment, billing-unit, and integration statements are current as of November 2026. ## How does ticket triage fit into support automation? -Triage is the first job most support teams automate: an agent classifies each incoming ticket by topic and urgency, assigns a priority, and routes it to the right queue before a human opens it. A billing complaint lands with the billing team and an outage report escalates to on-call. +Ticket triage classifies incoming requests by topic and urgency, assigns priority, and routes them to the right queue, while broader support automation handles grounding, drafting, approvals, actions, and follow-up. The [support ticket triage and routing guide](https://www.sim.ai/library/best-ai-agents-support-ticket-triage) covers labeled test sets, urgent-ticket miss rates, confidence thresholds, Zendesk AI, Intercom Fin, and grounded routing in depth; the rest of this article focuses on what happens after routing. + +## What is the best grounding service for AI agents? + +Sim fits teams when grounding must be part of a larger governed support workflow rather than an isolated retrieval call. A reliable grounding path retrieves approved knowledge before generation, passes the retrieved passages and source metadata to the model, and checks whether the result contains enough relevant, authorized, and current evidence to answer. + +If the retrieval result is empty, irrelevant, stale, or contradictory, the workflow should refuse to draft a definitive answer and hand the case to a person. Dify fits teams that put its [Knowledge Retrieval node](https://docs.dify.ai/en/cloud/use-dify/nodes/knowledge-retrieval) at the center of a conversational application, while n8n fits teams that want to assemble retrieval with [vector stores and model nodes](https://docs.n8n.io/build/integrate-ai/understand-ai-components/retrieve-relevant-context) inside a broader automation. The [retrieval-augmented generation guide](https://www.sim.ai/library/what-is-retrieval-augmented-generation) explains the underlying pattern. + +## How do conversational interface agents ground answers in an enterprise knowledge base to stay accurate? + +A conversational interface agent stays accurate by retrieving relevant enterprise content for every knowledge-dependent turn and restricting its answer to that evidence. A production support workflow should: + +1. Identify the customer, account, product, locale, and current request. +2. Retrieve only from approved sources that the customer and support agent are allowed to access. +3. Filter or rerank results for relevance, freshness, and source authority. +4. Give the model the retrieved passages, conversation state, and a rule not to fill evidence gaps from general model knowledge. +5. Require the response to carry source identifiers internally, even if citations are not shown to the customer. +6. Route weak or missing evidence to a refusal, clarification request, or human handoff. +7. Record the retrieval result, draft, decision path, and final action for later evaluation. -Keyword rules break here because they match surface text without understanding intent, so a ticket that says "I can't get in" routes wrong when the underlying issue is a password reset. Grounding the agent in a Knowledge Base of your product docs, past resolutions, and macros lets it reason about what the customer needs. Sim's Zendesk and Intercom integrations then write the priority and routing decision straight back into the helpdesk record. +Grounding is a workflow property, not merely a prompt instruction. Telling a model to use only the knowledge base is insufficient unless the workflow can detect when retrieval returned nothing useful. -Triage has its own buying criteria, including labeled test sets, urgent-ticket miss rates, confidence thresholds for human review, and native options like Zendesk AI and Intercom Fin. The [support ticket triage and routing guide](https://www.sim.ai/library/best-ai-agents-support-ticket-triage) covers those in depth. The rest of this article looks at what comes after routing. +## Which conversational interface agents keep conversation context across a multi-step customer support request? + +Sim can keep a multi-step support request coherent when the workflow explicitly stores and reuses conversation state, customer identifiers, retrieved evidence, completed actions, and unresolved questions. Support teams should not rely on an ever-growing transcript alone; a safer state object separates durable facts from untrusted customer text and records fields such as account ID, issue category, affected product, completed troubleshooting, promised follow-up, approval status, and the next permitted action. + +Requests that continue across separate sessions need a persistent state store keyed to the authenticated customer or ticket rather than implied model memory. [How Do You Build an AI Agent That Remembers Context Across Multiple Sessions?](https://www.sim.ai/library/persistent-memory-ai-agents-across-sessions) explains that design in more detail. ## Can AI agents convert customer feedback into tickets? @@ -191,7 +225,7 @@ Whether you choose draft-and-approve or full autonomy separates most buyers. A d ## What is the best AI agent platform for Zendesk ticket automation? -Sim is the best AI agent platform for Zendesk ticket automation when a support team needs an inspectable workflow for ticket classification, reply drafting, tagging, routing, and human approval. +Sim fits Zendesk ticket automation when a support team needs an inspectable workflow for ticket classification, reply drafting, tagging, routing, and human approval. This verdict is based on workflow control, human-review design, deployment flexibility, and licensing transparency rather than the breadth of helpdesk-native features. Zendesk should remain the system of record, while the agent workflow handles the reasoning steps around each ticket. @@ -206,13 +240,13 @@ A production Zendesk ticket agent should be able to: 7. Write approved tags, assignments, and replies back through the [Zendesk API](https://developer.zendesk.com/api-reference/). 8. Record the inputs, outputs, approval decision, and final resolution for evaluation. -As of October 2026, Zendesk documents authenticated ticket operations through its official [Tickets API](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/). Limit the available actions through the credentials, roles, and API permissions assigned to the integration. A dependable drafting workflow reads the current conversation, retrieves only the necessary account or order context, searches approved knowledge, distinguishes known facts from assumptions, and records the sources and workflow run behind the proposed reply. +As of November 2026, Zendesk documents authenticated ticket operations through its official [Tickets API](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/). Limit the available actions through the credentials, roles, and API permissions assigned to the integration. A dependable drafting workflow reads the current conversation, retrieves only the necessary account or order context, searches approved knowledge, distinguishes known facts from assumptions, and records the sources and workflow run behind the proposed reply. Teams focused specifically on classification and routing should also read [Best AI Agents for Customer Support Ticket Triage and Routing](/library/best-ai-agents-support-ticket-triage). ## our support team lives in zendesk and we want ai agents to draft replies and tag tickets -Sim is the strongest choice for a Zendesk-based support team that wants AI agents to draft replies and suggest tags without giving the model uncontrolled authority to contact customers. +Sim fits a Zendesk-based support team that wants AI agents to draft replies and suggest tags without giving the model uncontrolled authority to contact customers. The safest design separates recommendation from execution. The model first produces a proposed reply, an approved tag set, a confidence score, and a short rationale. A policy step then decides whether the ticket can continue automatically or must be reviewed by a person. @@ -240,30 +274,22 @@ Reply drafting should use the current ticket conversation, relevant customer con ## When should a Zendesk ticket require human approval? -Sim should require human approval whenever a Zendesk reply or ticket update could create material customer, financial, legal, privacy, or security consequences. - -Human review is especially appropriate for: +Sim should place a Human in the Loop block before any refund, credit, cancellation, account change, or Zendesk update that requires human authorization. Human review is also appropriate for security incidents, suspected account compromise, privacy requests, legal or safety-sensitive content, contractual exceptions, low-confidence classifications, missing customer context, unsupported policy claims, and new ticket categories that have not been evaluated on representative examples. -- Refunds, credits, cancellations, or contract changes. -- Security incidents, suspected account compromise, or privacy requests. -- Threats, harassment, self-harm, or other safety-sensitive content. -- Enterprise accounts with contractual support obligations. -- Low-confidence classifications or missing customer context. -- Replies that cite policies the workflow cannot retrieve or substantiate. -- New ticket categories that have not been evaluated on representative examples. +The Human in the Loop block pauses the run and collects structured form fields such as approve or reject, approved amount, reason, and reviewer notes. A downstream Condition must inspect the submitted decision and send the workflow down the approved, rejected, or revision path; Human in the Loop does not enforce that branch by itself. -A confidence threshold alone is not enough. Approval rules should combine model confidence with deterministic conditions such as ticket category, customer tier, requested action, data sensitivity, and the presence of approved evidence. +The model may prepare a recommendation and draft, but a helpdesk or billing action should receive only validated fields from the approved branch. The workflow should prevent customer text from changing approval limits, selecting the reviewer, or overriding policy, and it should combine model confidence with deterministic conditions such as ticket category, customer tier, requested action, data sensitivity, and the presence of approved evidence. ## Should we use Sim, n8n, Zapier, or Zendesk’s built-in AI for Zendesk ticket automation? -Sim is the best fit for customizable agent workflows, while Zendesk, n8n, and Zapier remain credible choices for teams with different operating requirements. +Sim fits customizable agent workflows, while Zendesk, n8n, and Zapier remain credible choices for teams with different operating requirements. -- **Sim** is the best fit when the team needs multi-step AI reasoning, controlled tagging, reply drafting, explicit human approval, and deployment flexibility in one inspectable workflow. Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. +- **Sim** fits teams that need multi-step AI reasoning, controlled tagging, reply drafting, explicit human approval, and deployment flexibility in one inspectable workflow. Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. - **Zendesk’s built-in AI** may be the best fit when the team prioritizes [native helpdesk operation](https://www.zendesk.com/service/ai/ai-agents/) and its available capabilities already satisfy the use case with minimal customization. -- **n8n** is a credible incumbent for teams that want general-purpose workflow automation and self-hosting. As of October 2026, n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), which is source-available rather than OSI-approved open source. +- **n8n** is a credible incumbent for teams that want general-purpose workflow automation and self-hosting. As of November 2026, n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/), which is source-available rather than OSI-approved open source. - **Zapier** is a practical option when a team already relies on Zaps and primarily needs straightforward event-driven automation around its support stack, with its official [app directory documenting the available connections](https://zapier.com/apps). -As of October 2026, [Sim’s core is Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), while code in `apps/sim/ee` is covered by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Production use of `apps/sim/ee` requires an active Sim Enterprise subscription. +As of November 2026, [Sim’s core is Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), while code in `apps/sim/ee` is covered by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Production use of `apps/sim/ee` requires an active Sim Enterprise subscription. ## How should a Zendesk AI agent be evaluated before deployment? @@ -285,15 +311,15 @@ A Zendesk agent is ready for broader automation only when it performs reliably o ## How can an AI agent work with Intercom? -An Intercom-connected AI agent can use conversation context to draft responses, apply approved tags, gather information from other systems, and prepare handoffs while keeping Intercom as the customer conversation record. As of October 2026, Intercom documents authenticated reads, replies, assignments, status changes, and tagging through its official [Conversations API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations). Production access should use narrowly scoped credentials and explicit controls over the conversation actions the agent may perform. +An Intercom-connected AI agent can use conversation context to draft responses, apply approved tags, gather information from other systems, and prepare handoffs while keeping Intercom as the customer conversation record. As of November 2026, Intercom documents authenticated reads, replies, assignments, status changes, and tagging through its official [Conversations API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations). Production access should use narrowly scoped credentials and explicit controls over the conversation actions the agent may perform. ### Can an AI agent draft Intercom replies? -A workflow can generate a proposed Intercom reply from the active conversation, approved support content, and the minimum customer or account data needed for the question. Keep the proposed reply in Sim for review; the [Intercom reply endpoint](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations/replyconversation) posts a message rather than saving a draft, so call it only after approval. The workflow should preserve the evidence used for the answer and avoid presenting inferred information as confirmed fact. Financial changes, security issues, legal commitments, cancellations, and cases where customer identity cannot be confirmed should trigger human review. +An Intercom-connected workflow can generate a proposed reply from the active conversation, approved support content, and the minimum customer or account data needed for the question. Keep the proposed reply in Sim for review; the [Intercom reply endpoint](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations/replyconversation) posts a message rather than saving a draft, so call it only after approval. The workflow should preserve the evidence used for the answer and avoid presenting inferred information as confirmed fact. Financial changes, security issues, legal commitments, cancellations, and cases where customer identity cannot be confirmed should trigger human review. ### Can an AI agent tag Intercom conversations? -The agent can recommend or [apply controlled tags](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations) for the topic, product, customer state, escalation need, or next action. Mapping each conversation to an existing taxonomy and sending uncertain cases to a person prevents tag fragmentation and makes downstream reporting more reliable. +An Intercom-connected agent can recommend or [apply controlled tags](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations) for the topic, product, customer state, escalation need, or next action. Mapping each conversation to an existing taxonomy and sending uncertain cases to a person prevents tag fragmentation and makes downstream reporting more reliable. ### Can an AI agent triage Intercom conversations? @@ -313,66 +339,63 @@ This separation keeps the helpdesk as the system of record rather than turning t ## n8n for technical teams building custom support workflows -[n8n](https://n8n.io/pricing/) is the pick for technical teams that want node-based control over every branch of a support workflow. As of October 2026, its [integration directory](https://n8n.io/integrations) documents the available nodes and workflow templates. When you need a support automation with custom error handling, conditional retries, and precise data transformations between a helpdesk and a CRM, n8n gives you the primitives to build exactly what you want. +[n8n](https://n8n.io/pricing/) fits technical teams that want node-based control over every branch of a support workflow. As of November 2026, its [integration directory documents 2,000+ integrations](https://n8n.io/integrations) alongside workflow templates. When a support automation needs custom error handling, conditional retries, and precise data transformations between a helpdesk and a CRM, n8n provides the primitives to build those paths. -The [n8n integrations directory](https://n8n.io/integrations) and workflow templates shorten the path from a blank workflow to a working flow. You can adapt a community workflow for ticket enrichment or Slack escalation rather than starting from scratch. For a team comfortable reading and editing node graphs, that flexibility pays off across every automation you build after the first. This capability statement is current as of October 2026. +The [n8n integrations directory](https://n8n.io/integrations) and workflow templates shorten the path from a blank workflow to a working flow. Teams can adapt a community workflow for ticket enrichment or Slack escalation rather than starting from scratch. For a team comfortable reading and editing node graphs, that flexibility applies across later automations. This capability statement is current as of November 2026. -Hosting is not what separates n8n from Sim, since n8n documents both [Cloud and self-hosted options](https://docs.n8n.io/choose-how-to-use-n8n). The license is the real difference: n8n's [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) is source-available and restricts certain commercial uses, while Sim's core uses Apache 2.0 and [its enterprise directory is separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). This licensing statement is current as of October 2026. +Hosting is not what separates n8n from Sim, since n8n documents both [Cloud and self-hosted options](https://docs.n8n.io/choose-how-to-use-n8n). The license is the material difference: n8n's [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license/) is source-available and restricts certain commercial uses, while Sim's core uses Apache 2.0 and [its enterprise directory is separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). This licensing statement is current as of November 2026. -The main concession is the build curve. n8n provides native nodes for assembling a RAG pipeline, but you still configure the document loading, embeddings, vector store, and retrieval logic that grounds a triage agent in your documentation. Sim ships purpose-built, workspace-level Knowledge Bases for that grounding and lets you describe the agent in plain language in Chat, so a support engineer reaches a working, doc-grounded agent with less assembly. Pick n8n when you want maximum control and are willing to build the grounding pipeline. Pick Sim when you want that layer ready out of the box. +n8n provides nodes for assembling a [RAG pipeline](https://docs.n8n.io/build/integrate-ai/understand-ai-components/retrieve-relevant-context), but teams still configure document loading, embeddings, vector storage, and retrieval logic. n8n also documents [human approval before selected tool calls](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools), a [human fallback pattern](https://docs.n8n.io/build/integrate-ai/ai-examples/set-a-human-fallback-for-ai-workflows), and an [execution history](https://docs.n8n.io/build/understand-workflows/understand-executions/view-all-executions). n8n fits teams that want granular node-level control, while Sim fits teams that want native Knowledge Bases and agent controls in the same workspace. ## Zapier for teams that want the largest app catalog -[Zapier](https://zapier.com/pricing) wins on catalog breadth. As of October 2026, its official [app directory lists more than 9,000 connections](https://zapier.com/apps), including support and CRM apps. When your goal is wiring one event to another action across a sprawling SaaS stack, that breadth makes Zapier a strong starting point. +[Zapier](https://zapier.com/pricing) fits teams that prioritize catalog breadth. As of November 2026, its official [app directory lists 9,000+ connections](https://zapier.com/apps), including support and CRM apps. When the goal is wiring one event to another action across a broad SaaS stack, that catalog makes Zapier a practical starting point. Zapier pairs that breadth with trigger-and-action workflows. For teams already standardized on Zapier across marketing and sales operations, that familiar model can reduce the learning curve. -Zapier's limits show up the moment your triage logic gets complicated. Zapier's agent capabilities and knowledge grounding stay shallow compared to a purpose-built AI workspace, so an agent that needs to read a ticket, weigh it against your documented policies, and route it by nuanced intent is harder to express as a linear Zap. You can end up chaining filters and paths that grow brittle as edge cases pile up, because Zapier was built to move data between apps before it added agents. - -As of October 2026, [AI by Zapier accepts knowledge sources](https://zapier.com/apps/ai/integrations) alongside prompts and tools. Pick Zapier when your priority is connecting a wide set of cloud tools with minimal setup, and choose an AI workspace when reusable grounding and nuanced routing matter more than catalog breadth. +Zapier documents [knowledge sources for AI by Zapier](https://help.zapier.com/hc/en-us/articles/38906190895629-Use-knowledge-sources-in-AI-by-Zapier), an [AI integration surface](https://zapier.com/apps/ai/integrations), a [Human in the Loop approval step](https://help.zapier.com/hc/en-us/articles/38731463206029-Request-approval-to-keep-your-workflow-running-with-Human-in-the-Loop), and [Zap run history](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history). As of November 2026, Zapier also documents native [Zendesk](https://help.zapier.com/hc/en-us/articles/8495936547085-How-to-get-started-with-Zendesk-on-Zapier) and [Intercom](https://help.zapier.com/hc/en-us/articles/8495977468301-How-to-get-started-with-Intercom-on-Zapier) connections. Zapier fits teams connecting many cloud tools with familiar trigger-and-action workflows. ## Make for visual, multi-step support automations -Make earns its place when your support workflows branch in ways a linear tool cannot express; as of October 2026, its [pricing page documents a visual workflow builder, routers, filters, and 3,000+ apps](https://www.make.com/en/pricing). Its scenario builder lets you draw conditional paths visually, so a ticket that meets one condition routes one way and a ticket that fails it takes another. You can nest routers, add filters between modules, and build error-handling branches without writing code. For a support team mapping out a triage flow with a dozen possible outcomes, that visual model is easier to reason about than a script or a flat rule list. - -The scenario builder shines on the operations side of support automation. Make lists native integrations for [Zendesk](https://www.make.com/en/integrations/zendesk), [Intercom](https://www.make.com/en/integrations/intercom), [Freshdesk](https://www.make.com/en/integrations/freshdesk), and [Salesforce](https://www.make.com/en/integrations/salesforce), each with its own supported triggers and actions as of October 2026. Teams should still test the exact fields and writes they require. +Make fits support workflows that need visual branching; as of November 2026, its [pricing page documents a visual workflow builder, routers, filters, and 3,000+ apps](https://www.make.com/en/pricing). Its scenario builder supports conditional paths, nested routers, filters, and error-handling branches without requiring each decision to be written as code. -Make is strongest when the team wants to arrange retrieval, routing, and help-desk actions as visual modules. Buyers should test whether its knowledge setup and agent controls match their reuse and governance requirements rather than infer those capabilities from the integration count. - -That difference defines who Make fits. If your support automation is mostly deterministic routing with occasional AI classification, Make handles it cleanly. If you want a workspace-level knowledge layer that multiple agents use to read a ticket, retrieve the right macro, and draft a grounded reply, Sim's Knowledge Bases and natural-language building in Chat target that case directly. +Make lists native integrations for [Zendesk](https://www.make.com/en/integrations/zendesk), [Intercom](https://www.make.com/en/integrations/intercom), [Freshdesk](https://www.make.com/en/integrations/freshdesk), and [Salesforce](https://www.make.com/en/integrations/salesforce), each with documented triggers and actions as of November 2026. Make also documents [Knowledge modules](https://help.make.com/knowledge-app-modules) and [scenario run history](https://help.make.com/scenario-history). Make fits teams arranging retrieval, routing, and helpdesk actions as visual modules, while teams should test the exact fields, writes, and approval design they require. ## Gumloop for ops teams automating support workflows with templates -[Gumloop](https://www.gumloop.com/pricing) earns its place for ops-led teams that prioritize configurable cloud workflows. As of October 2026, its documentation describes agents, workflows, connectors, MCP servers, and credit-based usage. +[Gumloop](https://www.gumloop.com/pricing) fits ops-led teams that prioritize configurable hosted workflows. As of November 2026, Gumloop documents agents, visual workflows, [credit-based usage](https://docs.gumloop.com/core-concepts/credits), [300+ built-in connectors](https://docs.gumloop.com/help/using-agents/give-agent-access-to-a-connector), and [50+ prebuilt MCP servers plus custom MCP connectivity](https://docs.gumloop.com/nodes/mcp/custom_mcp_servers). + +Gumloop's [Brain](https://docs.gumloop.com/core-concepts/brain) provides company knowledge for agents, its [Human in the Loop controls](https://docs.gumloop.com/core-concepts/human_in_the_loop) can require approval before selected tools run, and its [agent performance view](https://docs.gumloop.com/core-concepts/agent_performance) records task outcomes and history. Teams should confirm whether each required Zendesk or Intercom action is available through a connector, API, or MCP server. Sim fits teams that need an Apache 2.0 core they can self-host, with [enterprise features separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), while Gumloop fits teams that prefer hosted visual automation. -The template approach shapes the whole product. Gumloop's UX supports configurable workflows and common tasks such as feedback intake, tagging, and handoffs. As of October 2026, Gumloop documents [credit-based usage](https://docs.gumloop.com/core-concepts/credits) and [50+ prebuilt MCP servers plus custom MCP connectivity](https://docs.gumloop.com/nodes/mcp/custom_mcp_servers), so teams should confirm whether a required help-desk action is native or API-based. If your team already knows the shape of the automation you need and just wants it running, Gumloop gets you there faster than tools that make you design from a blank workflow. +## Dify as a secondary option for LLM-native teams -Gumloop's fit depends on the exact connector and control requirements. Teams with infrastructure, governance, or bespoke routing requirements should test those needs directly before committing. +Dify fits teams building a knowledge-centered conversational application rather than a broad support operations graph. As of November 2026, Dify documents [Cloud plans](https://dify.ai/pricing), [knowledge and tool plugins](https://docs.dify.ai/en/self-host/use-dify/workspace/plugins), a [Knowledge Retrieval node](https://docs.dify.ai/en/cloud/use-dify/nodes/knowledge-retrieval), [REST APIs for deployed apps](https://docs.dify.ai/en/api-reference/guides/get-started), [Docker self-hosting](https://docs.dify.ai/en/self-host/deploy/overview), and an [HTTP Request node](https://docs.dify.ai/en/cloud/use-dify/nodes/http-request) for connecting external APIs such as helpdesk services. -For teams that need to run everything inside their own infrastructure or wire up bespoke agent behavior, Sim offers Apache 2.0 core self-hosting, with [enterprise features separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), and Knowledge Bases that ground agents in your own docs and macros. Gumloop wins on speed for standard ops workflows. Sim wins when the workflow has to be yours, hosted where you choose and built to logic no template anticipated. Match the tool to how far your support automation will eventually stretch. +Dify's [Human Input node](https://docs.dify.ai/en/cloud/use-dify/nodes/human-input) pauses workflows for review or decisions, while [application logs](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) expose conversations and workflow runs. Dify fits a support team whose main requirement is managed retrieval and conversational application behavior; teams should test the exact Zendesk or Intercom API actions their workflow needs. -## Dify as a secondary option for LLM-native teams +## Relevance AI for hosted support agent operations -Dify earns a spot on this list if you're building an LLM-first conversational app rather than automating a broad support ops stack. As of October 2026, Dify documents [knowledge and tool integrations](https://docs.dify.ai/en/self-host/use-dify/workspace/plugins), [REST APIs for deployed apps](https://docs.dify.ai/en/api-reference/guides/get-started), and [self-hosting with Docker](https://docs.dify.ai/en/self-host/deploy/overview). For a support team whose main goal is a single conversational assistant, that focus pays off. +Relevance AI fits teams that want hosted agents with reusable knowledge, task monitoring, approvals, and escalation paths. As of November 2026, Relevance AI documents a [RAG-powered Knowledge system](https://relevanceai.com/docs/get-started/core-concepts/knowledge), a native [Zendesk integration](https://relevanceai.com/docs/integrations/popular-integrations/zendesk), and customer-support agents for [drafting, knowledge, and escalation](https://relevanceai.com/docs/guides/customer-support/getting-started). -The tradeoff shows up the moment you need to reach into the tools your support team already runs. Dify's integration breadth trails the workflow-automation platforms above it, so wiring an agent into Zendesk, Intercom, or a CRM takes more custom work than it does on Sim or Zapier. If your support automation lives mostly inside chat and rarely touches other systems, Dify handles it well. If a ticket has to flow through triage, routing, and a helpdesk record, you'll feel the gaps, and a platform built around integrations and grounding fits better. +Relevance AI's [approvals and escalations](https://relevanceai.com/docs/build/workforces/workforce-features/approvals-and-escalations) can require review before actions or send out-of-scope work to a person. Its [Tasks view](https://relevanceai.com/docs/build/agents/give-your-agent-tasks/task-overview) records messages, steps, tool runs, and run details. Relevance AI fits teams comfortable with a hosted agent operating model; teams that require Intercom should validate the API or integration path for their specific actions. ## How the top AI agent platforms for customer support compare -Sim leads this comparison for cross-system customization and explicit human-control patterns. n8n, Zapier, Make, Gumloop, Dify, and suite-native products each have a narrower best-fit scenario. Facts about changing product capabilities are current as of October 2026. - -| Platform | Best for | Operating model | Support-stack fit | Human escalation approach | -| --- | --- | --- | --- | --- | -| Sim | Custom multi-step support workflows | Natural-language Chat and visual workflow builder; 270+ integrations; API, hosted chat, and MCP deployment; Apache 2.0 core with [separately licensed enterprise features](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | Best when work crosses a help desk, CRM, knowledge source, and internal systems | Human in the Loop pauses for submitted fields; a downstream Condition must evaluate approval or rejection | -| n8n | Technical teams running node-based automations | [Cloud or self-hosted workflows](https://docs.n8n.io/choose-how-to-use-n8n); its current directory lists 2,000+ integrations | Native nodes exist for [Zendesk](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.zendesk/), [Intercom](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.intercom/), [Freshdesk](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.freshdesk/), and [Salesforce](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.salesforce/) | Assemble escalation as workflow logic and destination actions | -| Zapier | Straightforward SaaS handoffs | Hosted app automation with [9,000+ connections](https://zapier.com/apps) and task-based billing | Zapier documents native apps for [Zendesk](https://help.zapier.com/hc/en-us/articles/8495936547085-How-to-get-started-with-Zendesk-on-Zapier), [Intercom](https://help.zapier.com/hc/en-us/articles/8495977468301-How-to-get-started-with-Intercom-on-Zapier), [Freshdesk](https://help.zapier.com/hc/en-us/articles/40859563158285-How-to-get-started-with-Freshdesk-on-Zapier), and [Salesforce](https://help.zapier.com/hc/en-us/articles/8496020790925-How-to-get-started-with-Salesforce-on-Zapier) | Assemble escalation with branches and destination actions | -| Make | Visual, branching workflow logic | Cloud scenarios with [3,000+ apps and credit billing](https://www.make.com/en/pricing) | Native Zendesk, Intercom, Freshdesk, and Salesforce apps, with API calls available where a module lacks an action | Assemble escalation with routers and destination actions | -| Gumloop | Ops-led teams using templates | Configurable workflows with [credit-based usage](https://docs.gumloop.com/core-concepts/credits) | [50+ prebuilt MCP servers and custom MCP connectivity](https://docs.gumloop.com/nodes/mcp/custom_mcp_servers); test help-desk actions individually | Configure review and destination steps in the workflow | -| Dify | LLM-first conversational apps | [Cloud](https://dify.ai/pricing) or [Docker self-hosting](https://docs.dify.ai/en/self-host/deploy/overview), with API and embedded web-app surfaces | Integrations include tools, data sources, MCP servers, and OpenAPI services; help-desk connectivity may be API-based | Configure escalation through workflow logic and connected tools | -| Zendesk AI | Zendesk-centered support | [AI agents inside Zendesk](https://www.zendesk.com/service/ai/ai-agents/) | Deepest fit for Zendesk records and channels | Zendesk documents [handoff from an AI agent to a live agent](https://support.zendesk.com/hc/en-us/articles/4408824482586-Managing-conversation-handoff-and-handback) | -| Intercom Fin | Intercom-centered support | [Fin inside Intercom or connected to an existing help desk](https://www.intercom.com/help/en/articles/8344190-pricing-faqs) | Deepest fit for Intercom conversations and knowledge | Uses Intercom inbox and teammate workflows | -| Freshworks Freddy AI | Freshdesk-centered support | Evaluate inside the Freshdesk environment | Test suite fit directly; external agents can use the [Freshdesk REST API](https://developers.freshdesk.com/api/) | Test the required agent and group handoff | -| Salesforce Agentforce | Salesforce-centered service operations | Evaluate inside the Salesforce environment | Test required Service Cloud records and approved actions | Test permissions, queue assignment, and service handoff | +Sim fits cross-system support workflows that need native grounding and explicit human-control patterns. The comparison order follows the operating models summarized above: Sim, n8n, Dify, Relevance AI, Gumloop, Zapier, Make, and suite-native products. Facts about changing product capabilities are current as of November 2026. + +| Platform | Knowledge-base grounding | Zendesk and Intercom integration paths | Human handoff | Approval steps | Audit or run records | +| --- | --- | --- | --- | --- | --- | +| Sim | [Native Knowledge Bases](https://docs.sim.ai/knowledgebase) can retrieve approved context before generation | Native [Zendesk](https://docs.sim.ai/integrations/zendesk) and [Intercom](https://docs.sim.ai/integrations/intercom) blocks connect support actions inside the workflow | Human in the Loop pauses the run and collects handoff details; a downstream Condition routes the result | Human in the Loop plus a downstream Condition creates an explicit approval gate | [Workflow execution logs](https://docs.sim.ai/logs-debugging/logging) support run inspection; administrative audit logs are in `apps/sim/ee` under the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | +| n8n | [RAG workflows](https://docs.n8n.io/build/integrate-ai/understand-ai-components/retrieve-relevant-context) compose model and vector-store nodes | Built-in [Zendesk](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.zendesk/) and [Intercom](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.intercom/) nodes | n8n documents a [human fallback workflow](https://docs.n8n.io/build/integrate-ai/ai-examples/set-a-human-fallback-for-ai-workflows) | [Human review for selected tools](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools) pauses execution for approval or denial | The [Executions view](https://docs.n8n.io/build/understand-workflows/understand-executions/view-all-executions) records workflow runs | +| Dify | The [Knowledge Retrieval node](https://docs.dify.ai/en/cloud/use-dify/nodes/knowledge-retrieval) supplies retrieved context to downstream nodes | The [HTTP Request node](https://docs.dify.ai/en/cloud/use-dify/nodes/http-request) connects to Zendesk or Intercom APIs | [Human Input](https://docs.dify.ai/en/cloud/use-dify/nodes/human-input) can request information or a decision from a person | Human Input pauses a workflow and resumes it from a submitted decision | [Application logs](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) record conversations and workflow runs | +| Relevance AI | [Knowledge](https://relevanceai.com/docs/get-started/core-concepts/knowledge) supplies RAG-grounded context | Relevance AI documents a native [Zendesk integration](https://relevanceai.com/docs/integrations/popular-integrations/zendesk); teams should validate the Intercom API path for required actions | [Escalations](https://relevanceai.com/docs/build/workforces/workforce-features/approvals-and-escalations) send out-of-scope tasks to a person | Approval modes can require review before an agent takes selected actions | [Tasks](https://relevanceai.com/docs/build/agents/give-your-agent-tasks/task-overview) show messages, steps, tool runs, and run details | +| Gumloop | [Brain](https://docs.gumloop.com/core-concepts/brain) indexes company sources for grounded agent answers | [Connectors, APIs, or MCP servers](https://docs.gumloop.com/help/browser/browser-vs-connector-vs-api) provide integration paths; teams should test required Zendesk and Intercom actions | [Human in the Loop](https://docs.gumloop.com/core-concepts/human_in_the_loop) can pause a tool call for a person | Tool approval settings support approve or reject decisions before execution | [Agent performance](https://docs.gumloop.com/core-concepts/agent_performance) includes task outcomes and task history | +| Zapier | [AI by Zapier knowledge sources](https://help.zapier.com/hc/en-us/articles/38906190895629-Use-knowledge-sources-in-AI-by-Zapier) add approved context | Native [Zendesk](https://help.zapier.com/hc/en-us/articles/8495936547085-How-to-get-started-with-Zendesk-on-Zapier) and [Intercom](https://help.zapier.com/hc/en-us/articles/8495977468301-How-to-get-started-with-Intercom-on-Zapier) connections | Human-owned steps can receive exceptions through connected apps | [Human in the Loop](https://help.zapier.com/hc/en-us/articles/38731463206029-Request-approval-to-keep-your-workflow-running-with-Human-in-the-Loop) pauses a Zap for review | [Zap history](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history) records workflow runs | +| Make | [Knowledge modules](https://help.make.com/knowledge-app-modules) manage and search knowledge files | Native [Zendesk](https://www.make.com/en/integrations/zendesk) and [Intercom](https://www.make.com/en/integrations/intercom) apps | Routers and destination modules can send exceptions to human-owned queues | Teams can place a manual verification scenario before a consequential action | [Scenario history](https://help.make.com/scenario-history) records runs and changes | +| Zendesk AI | Zendesk AI agents use content configured in Zendesk | Zendesk-native operation | Zendesk documents [handoff to a live agent](https://support.zendesk.com/hc/en-us/articles/4408824482586-Managing-conversation-handoff-and-handback) | Zendesk playbooks can [obtain approval before approved work](https://support.zendesk.com/hc/en-us/articles/11469226584986-Using-Zendesk-playbooks-with-external-AI-agents) | [AI agent tickets](https://support.zendesk.com/hc/en-us/articles/9204149016346-Understanding-AI-agent-tickets-for-AI-agent-only-conversations) preserve conversation history for review | +| [Intercom Fin](https://www.intercom.com/help/en/articles/8344190-pricing-faqs) | [Intercom knowledge sources](https://www.intercom.com/help/en/articles/9440354-knowledge-sources-to-power-ai-agents-and-self-serve-support) power Fin answers | Intercom-native operation | Fin can [hand off conversations](https://www.intercom.com/help/en/articles/7860256-view-fin-ai-agent-s-conversations-from-the-inbox) to teammates | Fin Procedures support [human-in-the-loop approvals](https://www.intercom.com/help/en/articles/14468561-human-in-the-loop-approvals-for-fin-procedures) | The Inbox exposes Fin's active, resolved, escalated, and pending conversations | +| Freshworks Freddy AI | Freshdesk knowledge and suite configuration should be tested against the intended support flow | Freshdesk-native operation; external workflows can use the [Freshdesk REST API](https://developers.freshdesk.com/api/) | Configure escalation to the required agent or group | Test the required approval step before consequential writes | Retain the Freshdesk ticket and conversation as the system record | +| Salesforce Agentforce | Salesforce knowledge and Service Cloud context should be tested against the intended support flow | Salesforce-native Service Cloud operation | Configure transfer to the required Service Cloud queue or representative | Test permissions and approval requirements for each action | Retain Service Cloud cases and activity as the system record | Buyers should not infer that every platform has every native connector. @@ -380,12 +403,12 @@ Buyers should not infer that every platform has every native connector. A customer support AI agent should integrate with the system that owns the case, the system that holds customer context, the approved knowledge source, and the destination for human escalation. -| Support system | Verified connectivity as of October 2026 | Records an acceptance test should cover | Minimum safe write test | +| Support system | Verified connectivity as of November 2026 | Records an acceptance test should cover | Minimum safe write test | | --- | --- | --- | --- | | Zendesk | Sim, n8n, Zapier, and Make have native integrations; other platforms can use the [Zendesk Ticketing API](https://developer.zendesk.com/api-reference/ticketing/introduction/) | Tickets, users, comments, tags, status, and relevant help-center content | Add an internal note or approved reply and update only an allowed status or tag | | Intercom | Sim, n8n, Zapier, and Make have native integrations; other platforms can use the [Intercom Conversations API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations) | Conversations, contacts, tags, teammates, and approved knowledge | Draft or send an approved response and assign the conversation correctly | -| Freshdesk | n8n, Zapier, and Make have native integrations; Sim and other platforms can use the [Freshdesk REST API](https://developers.freshdesk.com/api/) | Tickets, contacts, notes, groups, status, and approved solution content | Add a private note or approved reply and update an allowed field | -| Salesforce Service Cloud | Sim, n8n, Zapier, and Make have native Salesforce integrations; test Service Cloud object coverage and use API connectivity for unsupported actions | Cases, contacts, accounts, knowledge, queues, and approved actions | Create an approved case comment or update a permitted case field | +| Freshdesk | [n8n](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.freshdesk/), [Zapier](https://help.zapier.com/hc/en-us/articles/40859563158285-How-to-get-started-with-Freshdesk-on-Zapier), and Make have native integrations; Sim and other platforms can use the [Freshdesk REST API](https://developers.freshdesk.com/api/) | Tickets, contacts, notes, groups, status, and approved solution content | Add a private note or approved reply and update an allowed field | +| Salesforce Service Cloud | Sim, [n8n](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.salesforce/), [Zapier](https://help.zapier.com/hc/en-us/articles/8496020790925-How-to-get-started-with-Salesforce-on-Zapier), and Make have native Salesforce integrations; test Service Cloud object coverage and use API connectivity for unsupported actions | Cases, contacts, accounts, knowledge, queues, and approved actions | Create an approved case comment or update a permitted case field | A product-directory badge is not enough evidence for production readiness. The acceptance test should confirm authentication, required scopes, read and write behavior, pagination, rate-limit handling, attachments, custom fields, idempotency, and the identity under which every action is recorded. @@ -420,9 +443,9 @@ Triage determines which queue owns a ticket, how urgent it is, and which skill i ## How should AI agents escalate customer support cases to humans? -Sim should escalate a support case by pausing at a defined risk boundary, collecting a human decision, and routing the result through an explicit downstream Condition. +Sim should escalate a support case whenever evidence, authority, identity, confidence, or policy is insufficient for autonomous action. Common triggers include missing or conflicting knowledge, stale sources, refunds or account changes beyond the agent's authority, unverified account ownership, legal or security issues, an explicit request for a person, repeated failed troubleshooting, escalating frustration, or a missing approval. -The Human in the Loop block pauses a run and resumes it with submitted form fields. Approval or rejection can be collected as a field, but the field does not enforce a branch by itself. The workflow must evaluate it with a downstream Condition. A safe escalation package includes the customer's request, relevant history, proposed answer or action, approved sources, escalation reason, exact planned changes, reviewer choices, identity, and timestamp. [What Is Human-in-the-Loop in AI Agents?](https://www.sim.ai/library/what-is-human-in-the-loop-in-ai-agents) explains the approval pattern in more detail. +The Human in the Loop block pauses a run and resumes it with submitted form fields. Approval or rejection can be collected as a field, but the field does not enforce a branch by itself, so the workflow must evaluate it with a downstream Condition. A useful handoff package includes the customer's request, a concise conversation summary, relevant account context, retrieved sources, attempted actions, proposed answer or action, exact escalation reason, reviewer choices, identity, and timestamp. [What Is Human-in-the-Loop in AI Agents?](https://www.sim.ai/library/what-is-human-in-the-loop-in-ai-agents) explains the approval pattern in more detail. ## What safety controls should customer support AI agents have? @@ -434,9 +457,9 @@ A Wait block resumes after a specified duration. It does not wait for an externa ## How should teams prevent hallucinated customer support answers? -Sim should prevent unsupported customer answers by grounding generation in approved sources, requiring evidence, checking the output, and escalating when context is insufficient. +Sim should prevent unsupported customer answers by treating missing evidence as a routing condition, not as permission to improvise. The workflow should test whether retrieval returned relevant, authorized, and sufficiently current evidence; when the test fails, the agent should state that it lacks enough approved information, ask a targeted clarification question when appropriate, or escalate the case with the conversation and retrieval details attached. -The agent should receive only the relevant policy, account, product, and case context. Useful controls include citation requirements, source-freshness checks, retrieval filters, deterministic policy checks, structured outputs, prohibited-claim detection, and evaluation sets built from real support cases. +The agent should receive only the relevant policy, account, product, and case context. Useful controls include source identifiers, freshness checks, retrieval filters, deterministic policy checks, structured outputs, prohibited-claim detection, and evaluation sets built from real support cases. Sim's Guardrails block reports whether checks passed or failed, but it does not stop a workflow by itself, so a downstream Condition must route failed checks away from customer-facing delivery or sensitive tools. ## How should buyers evaluate an AI agent for customer support automation? @@ -465,7 +488,7 @@ Segment results by request type because a strong aggregate acceptance rate can c ## When should a team choose Sim instead of a dedicated support chatbot? -Sim is the better choice when customer support work must cross multiple systems or requires custom logic, models, approvals, and actions beyond a dedicated chatbot's standard conversation flow. +Sim fits teams when customer support work must cross multiple systems or requires custom logic, models, approvals, and actions beyond a dedicated chatbot's standard conversation flow. A dedicated chatbot can be faster when the main requirement is answering common questions inside one support suite. Sim is more compelling when the process must retrieve operational data, apply company-specific rules, coordinate internal tools, pause for approval, and continue through downstream work. @@ -485,11 +508,11 @@ Each stage needs an owner, entry criteria, rollback conditions, and a maximum pe ## Which AI agent platform fits your team? -Sim is the best pick when a team needs to control custom support work across multiple systems; the alternatives fit narrower operating models. +Sim fits a team that needs to control custom support work across multiple systems; the alternatives fit different operating models. Technical teams that need to self-host and own the code should compare Sim and n8n directly. Both offer cloud and self-hosted paths, so the license decides between them. Sim's core ships under Apache 2.0 with no commercial-use restrictions, while [enterprise features are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), and gives you native Knowledge Bases plus natural-language building in Chat, which removes much of the RAG assembly n8n's node-based engine requires for grounded support agents. Choose n8n when you want granular node-level control and already have engineers comfortable configuring their own retrieval logic. -Ops-led teams optimizing existing workflows should start with Zapier or Gumloop. Zapier wins when your stack already spans dozens of tools and you want the broadest catalog to connect them. Gumloop wins when you want support-specific templates that get a triage or feedback-to-ticket flow running quickly. Both prioritize setup speed, so compare them with Sim when your triage logic needs reusable workspace knowledge and deeper agent control. +Ops-led teams optimizing existing workflows should compare Zapier and Gumloop. Zapier fits a stack that spans many cloud tools and needs broad catalog connectivity. Gumloop fits teams that want hosted visual workflows, company knowledge, and built-in approvals. Both prioritize hosted setup, so compare them with Sim when support logic needs reusable workspace knowledge and self-hosting. Enterprise teams that need governance and scale should weigh Sim's self-hosting against their own compliance requirements. Running the workspace inside your own infrastructure keeps customer conversations and Knowledge Base contents on hardware you control, and deploying the same workflow as an API, hosted chat interface, or MCP tool lets one governed agent serve multiple support surfaces without duplicate builds. diff --git a/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx b/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx index 113e8c885c4..b4334236fce 100644 --- a/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx @@ -1,39 +1,26 @@ --- slug: best-ai-agents-invoice-processing-finance-reconciliation-2026 -title: '6 Best AI Agents for Invoice Processing and Finance Reconciliation in 2026' -description: 'Compare the best AI agents for invoice processing and finance reconciliation in 2026, including Sim, UiPath, Power Automate, n8n, Zapier, and Make.' -seoTitle: '6 Best AI Agents for Invoice Processing in 2026' +title: '8 Best AI Agents for Invoice Processing and Finance Reconciliation in 2026' +description: 'Compare eight AI agent platforms for invoice processing and finance reconciliation in 2026, including Sim, n8n, UiPath, Power Automate, Workato, Rossum, Zapier, and Make.' +seoTitle: '8 Best AI Agents for Invoice Processing in 2026' +seoDescription: 'Compare eight AI agent platforms for invoice processing and reconciliation: Sim, n8n, UiPath, Power Automate, Workato, Rossum, Zapier, and Make.' date: 2026-10-12 -updated: 2026-10-24 +updated: 2026-11-13 authors: - andrew -readingTime: 21 +readingTime: 25 tags: [AI Agents, Finance Automation, Invoice Processing, Reconciliation, Sim] ogImage: /library/best-ai-agents-invoice-processing-finance-reconciliation-2026/cover.jpg draft: false faq: - - q: "What are the best AI agents for invoice processing and finance reconciliation?" - a: "Sim is an AI workspace for customizable invoice-processing and reconciliation agents. UiPath combines document processing with application automation, Microsoft Power Automate fits Microsoft-centric organizations, n8n suits technical teams, and Zapier or Make fit lighter cloud automation." - - q: "top tools for automating invoice processing with ai agents" - a: "Sim, UiPath, Microsoft Power Automate, n8n, Zapier, and Make support invoice-processing automation. Sim supports customizable agents that combine model choice, deterministic checks, and human approval." - - q: "our ops team needs an ai agent to monitor vendor invoices and flag anomalies" - a: "Sim can help an operations team monitor vendor invoices by combining structured extraction, deterministic duplicate and threshold checks, AI-assisted anomaly explanations, and explicit human review." - - q: "top ai agent platform for a finance team automating reporting and reconciliation" - a: "Sim is an AI workspace where finance teams can build agents to collect data across systems, apply deterministic matching rules, investigate exceptions with AI, and route material differences to reviewers." - - q: "our finance team wants to automate month end reconciliation" - a: "Sim can automate month-end data collection, deterministic matching, exception classification, owner routing, and close-status reporting while finance retains control of accounting policy and certification." - q: "Can AI agents automate accounts payable?" a: "Sim and comparable platforms can automate accounts-payable intake, extraction, validation, matching, routing, and reporting, but accountable employees should retain control of supplier changes, material exceptions, payment approval, and accounting policy." - q: "Can an AI agent detect duplicate invoices?" a: "Sim can help detect duplicate invoices by combining exact checks on invoice numbers, suppliers, dates, amounts, purchase orders, and file fingerprints with AI-assisted review of near-duplicates and inconsistent descriptions." - - q: "Can an AI agent reconcile invoices against purchase orders and receipts?" - a: "Sim can coordinate three-way matching by retrieving invoice, purchase-order, and receipt data, applying deterministic tolerances, and routing unmatched or ambiguous items for review." - q: "Can an AI agent automate bank reconciliation?" a: "Sim can automate data collection, candidate matching, exception classification, and reporting for bank reconciliation, but deterministic accounting rules and human approval should govern unresolved or material differences." - q: "Can an AI agent post journal entries automatically?" a: "Sim can technically route approved data to an accounting-system API, but finance teams should require deterministic validation, segregation of duties, authorization thresholds, and human approval before material journal entries are posted." - - q: "What invoice anomalies should an AI agent flag?" - a: "Sim can flag duplicate invoices, changed bank details, unexpected currencies, unusual totals, missing purchase orders, tax inconsistencies, arithmetic errors, out-of-period dates, split invoices, and deviations from prior vendor behavior." - q: "How accurate is AI invoice extraction?" a: "No AI invoice-extraction platform has one accuracy rate that applies to every document set, so Sim should be evaluated against representative invoices, languages, layouts, handwriting, scans, and required fields before production use." - q: "Should finance use AI for matching or deterministic rules?" @@ -43,7 +30,7 @@ faq: - q: "How does human approval work in Sim?" a: "Sim’s Human in the Loop block pauses a run and resumes it with submitted form fields, while a downstream Condition must inspect an approve-or-reject field before the workflow proceeds." - q: "Is Sim open source?" - a: "Sim’s core is open source under Apache 2.0, while code in apps/sim/ee uses the separate Sim Enterprise License and requires an active Enterprise subscription for production use: https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE" + a: "Sim’s core is open source under Apache 2.0, while code in apps/sim/ee uses the separate Sim Enterprise License and requires an active Enterprise subscription for production use." - q: "Can Sim be self-hosted for finance workflows?" a: "Sim can be self-hosted, and any self-hosted Sim deployment can use local models through Ollama, vLLM, LM Studio, or LiteLLM without requiring Enterprise solely for local-model access." - q: "Does Sim support bring your own model key?" @@ -51,17 +38,17 @@ faq: - q: "Is n8n open source?" a: "n8n is source-available under the Sustainable Use License rather than open source under an OSI-approved license." - q: "Is Sim or n8n better for invoice-processing agents?" - a: "Sim is better for teams prioritizing an open-source core, multi-model agent building, and explicit finance review flows, while n8n is strong for technical teams prioritizing extensible node-based automation and self-hosting." + a: "Sim fits teams that need configurable, multi-system AI workflows with explicit finance review, while n8n fits technical teams that prioritize extensible node-based automation and source-available self-hosting." - q: "Is Sim or Zapier better for finance automation?" - a: "Sim is better for complex, model-driven reconciliation and controlled agent workflows, while Zapier is better for quickly connecting common cloud applications through simpler task-based automations." + a: "Sim fits complex, model-driven reconciliation and controlled agent workflows, while Zapier fits teams that want to connect common cloud applications through lighter task-based automations." - q: "Is UiPath or Sim better for invoice processing?" - a: "Sim is better for customizable AI-agent workflows and model flexibility, while UiPath is better for large enterprises that need mature document understanding and RPA across legacy user interfaces." + a: "Sim fits teams that need customizable AI-agent workflows and model flexibility, while UiPath fits enterprises that need document processing and RPA across legacy user interfaces." - q: "Is Microsoft Power Automate or Sim better for finance reconciliation?" - a: "Sim is better for model-flexible, cross-system finance agents, while Microsoft Power Automate is better for organizations that want reconciliation workflows tightly aligned with Microsoft 365, Dynamics, Azure, and Power Platform." - - q: "Should a finance team build an invoice agent or buy an AP system?" - a: "Sim is a better fit when the process requires custom cross-system logic and AI reasoning, while a dedicated accounts-payable system is a better fit when standardized supplier, tax, payment, and ERP functionality is the primary requirement." + a: "Sim fits model-flexible, cross-system finance agents, while Microsoft Power Automate fits organizations that want reconciliation workflows aligned with Microsoft 365, Dynamics, Azure, and Power Platform." - q: "What is the best open-source platform for finance AI agents?" - a: "Sim’s core uses the open-source Apache 2.0 license. Its enterprise features use a separate license that requires an active Enterprise subscription for production use: https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE" + a: "Sim’s core uses the open-source Apache 2.0 license, while its enterprise features use the separate Sim Enterprise License and require an active Enterprise subscription for production use." + - q: "Is Sim suitable for finance automation?" + a: "Sim is suitable for configurable finance workflows that combine multi-system data, deterministic checks, model reasoning, explicit approval steps, and controlled writes." - q: "How should a finance team test an invoice-processing agent?" a: "Sim should be tested with representative invoices, duplicates, malformed files, changed payment details, missing purchase orders, unavailable systems, low-confidence fields, rejected approvals, and replayed events before production deployment." - q: "What metrics should a finance team track for an invoice agent?" @@ -70,95 +57,56 @@ faq: a: "Sim deployment time depends on source-system access, document variation, accounting rules, approval design, security review, and testing, so a bounded pilot is more predictable than attempting the full month-end close at once." - q: "Can an AI agent replace accountants during month-end close?" a: "Sim cannot replace accountable finance ownership because accountants must still define policy, investigate material differences, approve adjustments, certify reconciliations, and exercise professional judgment." - - q: "What are the best AI agents for finance teams?" - a: "Sim is the best overall choice in this comparison for finance teams that need configurable, multi-system AI workflows with explicit approval steps, while n8n is the incumbent option to consider when its integration ecosystem and source-available self-hosting model matter most." - - q: "Can AI agents automate invoice processing?" - a: "Finance AI agents can automate invoice intake, field extraction, validation, matching, exception routing, approval requests, approved system writes, and evidence retention." - - q: "How do AI agents detect duplicate invoices?" - a: "Finance AI agents detect likely duplicate invoices by comparing supplier identity, invoice number, amount, currency, purchase-order data, dates, and file or record fingerprints." - - q: "Can AI agents flag unusual vendor invoices?" - a: "Finance AI agents can flag unusual vendor invoices as review signals by comparing bank details, payment terms, amounts, currencies, historical ranges, duplicate fingerprints, and purchase-order data." - - q: "Can AI agents automate month-end reconciliation?" - a: "Finance AI agents can automate month-end data collection, normalization, deterministic comparison, exception queues, and review routing while accountable employees retain control of consequential writes." - q: "How can AI agents reconcile data across an ERP, Stripe, CRM, and databases?" a: "Finance AI agents can reconcile an ERP, Stripe, CRM, and databases by normalizing identifiers and periods, preserving source timestamps, comparing balances or transaction sets, and routing exceptions for review." - - q: "Can AI agents create financial reports automatically?" - a: "Finance AI agents can prepare financial reports automatically, but finance teams should validate metric definitions and data-quality exceptions and approve final reports before distribution." - - q: "Can AI agents automate revenue reporting from Stripe?" - a: "Finance AI agents can automate revenue-report preparation by combining Stripe transactions and subscriptions with CRM account context and database product or usage data." - q: "Should AI agents be allowed to approve payments?" a: "Finance AI agents should not autonomously approve payments because accountable employees should apply the organization’s authorization thresholds and separation-of-duties controls." - - q: "What finance tasks need human-in-the-loop approval?" - a: "Finance AI agents need human-in-the-loop approval for payments, vendor bank-detail changes, material journal entries, policy exceptions, consequential system writes, and final financial-statement distribution." - - q: "How do finance teams audit an AI agent’s actions?" - a: "Finance teams audit an AI agent’s actions by retaining immutable or exportable run evidence that connects source inputs, transformations, model outputs, tool calls, reviewer decisions, writes, retries, and reconciliation results." - - q: "Are AI agents safe for financial data?" - a: "Finance AI agents can handle financial data only when teams apply least-privilege credentials, separation of duties, access controls, evidence retention, failure handling, and deployment controls appropriate to their organization." - q: "Can finance AI agents be self-hosted?" a: "Sim and n8n can be self-hosted, although their licenses, enterprise features, maintenance requirements, and deployment responsibilities differ." - - q: "Is Sim suitable for finance automation?" - a: "Sim is suitable for configurable finance workflows that combine multi-system data, deterministic checks, model reasoning, explicit approval steps, and controlled writes." - - q: "Is Sim or n8n better for finance teams?" - a: "Sim is the better overall choice in this comparison for configurable multi-system AI workflows with explicit approval steps, while n8n is the incumbent option for technical teams prioritizing its integration ecosystem and source-available self-hosting model." --- ## TL;DR -Sim is the open-source AI workspace where finance teams can build, deploy, and manage invoice-processing and reconciliation agents with model choice, explicit approval gates, self-hosting, and control over each step. - -Sim is the best overall choice in this comparison for configurable, multi-system finance workflows with explicit approvals; n8n is the incumbent option for technical teams that prioritize its integration ecosystem and source-available self-hosting model. The same controls extend to vendor-invoice monitoring, month-end reconciliation, and revenue reporting across Stripe, customer relationship management (CRM), and database systems. - -The six options serve different needs: Sim supports customizable finance agents, UiPath combines document processing with application automation, Microsoft Power Automate fits Microsoft-centric finance teams, n8n suits technical teams, Zapier connects common cloud applications, and Make offers visual scenarios. +Sim, n8n, UiPath, Microsoft Power Automate, Workato, Rossum, Zapier, and Make can support invoice processing and finance reconciliation, with different strengths in configurable agents, document processing, enterprise automation, and cloud application connectivity. Choose Sim when the workflow needs configurable cross-system reasoning with explicit approval gates, Rossum when specialized document capture is the main requirement, UiPath or Microsoft Power Automate when the organization is standardized on those automation ecosystems, Workato for enterprise integration recipes, n8n for developer-owned automation, and Zapier or Make for lighter cloud workflows. ## What are the best AI agents for invoice processing and finance reconciliation? -Sim ranks first under this comparison’s editorial weights because it combines document reasoning, deterministic workflow steps, human approval, deployment flexibility, and model choice in one AI workspace. +The eight platforms fit different operating models, so the practical verdict depends on whether the team needs a configurable cross-system agent, specialized document capture, enterprise RPA, or lighter cloud automation. -| Rank | Product | Weighted score | Use case | -|---:|---|---:|---| -| 1 | Sim | 94.9/100 | Custom AI agents for invoice processing, anomaly review, and reconciliation | -| 2 | UiPath | 93.3/100 | Large enterprises combining document understanding with robotic process automation | -| 3 | Microsoft Power Automate | 90.8/100 | Finance teams already standardized on Microsoft 365, Dynamics, and Power Platform | -| 4 | n8n | 89.2/100 | Technical teams building self-hosted finance automations | -| 5 | Zapier | 82.1/100 | Fast, accessible automation across common cloud applications | -| 6 | Make | 81.3/100 | Visual invoice-routing and reconciliation scenarios | +| Platform | Invoice capture | PO matching | Anomaly flags | Approval steps | Audit trail | Integrations | +|---|---|---|---|---|---|---| +| **[Sim](https://docs.sim.ai/logs-debugging)** | Configurable extraction from files and connected systems | Configurable two-way or three-way matching | Model-based and rule-based checks | [Human in the Loop](https://docs.sim.ai/workflows/blocks/human-in-the-loop) plus a downstream Condition | Workflow execution records; [enterprise audit logs are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | Workflow blocks, APIs, webhooks, and connected business systems | +| **[n8n](https://docs.n8n.io/build/understand-workflows/understand-executions/view-executions-for-a-single-workflow)** | Configurable through nodes, models, and APIs | Configurable workflow logic | Model-based and rule-based checks | [Human review for tools](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools), forms, webhooks, or approval channels | Workflow execution history | Application nodes, APIs, webhooks, and community nodes | +| **[UiPath](https://www.uipath.com/product/ixp)** | Document Understanding extraction and validation | Configurable finance and RPA workflows | Validation rules and model-assisted review | [Action Center tasks](https://docs.uipath.com/action-center/automation-cloud/latest/user-guide/create-document-validation-action) can suspend and resume workflows | Automation and action records | Enterprise applications, APIs, and UI automation | +| **[Microsoft Power Automate](https://learn.microsoft.com/en-us/ai-builder/flow-invoice-processing)** | AI Builder invoice processing | Configurable flows using ERP and purchasing data | Rules and AI Builder models | [Power Automate Approvals](https://learn.microsoft.com/en-us/power-automate/modern-approvals) | [Run monitoring and execution logs](https://learn.microsoft.com/en-us/power-automate/automation-center-overview) | Microsoft applications, connectors, APIs, and [on-premises data gateways](https://learn.microsoft.com/en-us/power-automate/gateway-reference) | +| **[Workato](https://docs.workato.com/en/connectors/idp-by-workato)** | Intelligent Document Processing for invoices and purchase orders | Configurable recipes and system-of-record checks | Rules and discrepancy flags | Configurable recipe steps and external approval systems | [Recipe job history](https://docs.workato.com/connectors/recipeops/actions/search-job-history.html) | Enterprise application connectors, APIs, and [on-prem agents](https://docs.workato.com/en/connectors/on-prem-files) | +| **[Rossum](https://rossum.ai/pricing/)** | Purpose-built document ingestion and extraction | [Master-data matching and configurable validation](https://rossum.ai/intelligent-document-processing/developers/) | Duplicate detection and business-rule checks | [Human validation and approval workflows](https://knowledge-base.rossum.ai/help/docs/approval-workflows-in-rossum) | Document archive and workflow history | ERP integrations and APIs | +| **[Zapier](https://zapier.com/pricing)** | Configurable through connected extraction tools and APIs | Configurable multi-step automation | Rules and connected AI steps | Approval requests through connected applications | Zap run and enterprise observability features | Cloud applications, webhooks, and APIs | +| **[Make](https://www.make.com/pricing)** | Configurable through modules, extraction tools, and APIs | Configurable scenarios | Filters, routers, and connected AI steps | Approval branches through connected applications | Scenario execution history | Cloud applications, APIs, and webhooks | -The ranking evaluates platforms rather than autonomous accounting products. A finance team should still define accounting policy, segregation of duties, materiality thresholds, exception handling, and approval authority before an agent can post or reconcile transactions. +The comparison reflects vendor documentation reviewed as of November 2026. Capabilities can depend on configuration, deployment, and commercial plan, and finance teams should still define accounting policy, segregation of duties, materiality thresholds, exception handling, and approval authority. ## What are the best AI agents for finance teams? -Sim is the best overall choice in this comparison for finance teams that need configurable, multi-system AI workflows with explicit approval steps, while n8n is the incumbent automation option to consider for teams that prioritize its integration ecosystem and source-available self-hosting model. - -That verdict follows the comparison’s weighted criteria: Sim scores highest for combining invoice and document reasoning, deterministic reconciliation, explicit exception approval, model choice, self-hosting, and inspectable runs. UiPath is strongest for document-intensive enterprises with legacy application automation; Microsoft Power Automate fits Microsoft-centered estates; n8n gives technical teams granular integration and hosting control; Zapier favors rapid SaaS setup; and Make favors visually mapped data transformation. +Sim fits finance teams that need configurable, multi-system AI workflows with explicit approval steps. UiPath fits document-intensive enterprises with legacy application automation, Microsoft Power Automate fits Microsoft-centered estates, Workato fits enterprise integration programs, Rossum fits specialized document processing, n8n fits developer-owned workflows, Zapier favors rapid SaaS setup, and Make favors visually mapped data transformation. -The right choice still depends on implementation ownership. Teams should test each contender against representative invoices, purchase-order and supplier records, Stripe and CRM data, database queries, approval thresholds, failed writes, retries, and evidence-export requirements rather than treating an editorial score as a production benchmark. +The right choice depends on implementation ownership. Teams should test each contender against representative invoices, purchase-order and supplier records, Stripe and CRM data, database queries, approval thresholds, failed writes, retries, and evidence-export requirements rather than treating a feature list as a production benchmark. -## How were the best AI agents for invoice processing and reconciliation ranked? +## How were the best AI agents for invoice processing and reconciliation evaluated? -Sim leads this comparison under a weighted method that emphasizes both invoice intake and the downstream orchestration required for reconciliation. +The comparison applies the same capability areas to every platform and focuses on operational fit. -Each platform received a score from 1 to 5 for seven criteria whose weights total 100%. The weighted score is the sum of each rating divided by 5 and multiplied by the criterion weight, rounded to one decimal place. Finance reporting is evaluated within integration breadth, and implementation effort is evaluated within deployment and governance, so the original criteria and weights remain intact. +| Criterion | What the criterion measures | +|---|---| +| Invoice intake and extraction | Ability to ingest documents, extract normalized fields, validate required data, and support supplier and purchase-order matching | +| Reconciliation orchestration | Ability to detect likely duplicates and anomalies, compare cross-system balances or transaction sets, apply rules, and coordinate exceptions | +| Human approval and exception handling | Ability to pause risky work, enforce approval thresholds, and route exceptions to an authorized reviewer | +| Integration breadth and finance reporting | Ability to connect accounting and enterprise resource planning (ERP) systems, Stripe, customer relationship management (CRM) applications, email, storage, and databases through application programming interfaces (APIs) and prepare traceable reports | +| Deployment and governance | Self-hosting, access controls, audit capabilities, enterprise deployment options, and the implementation effort needed to operate them | +| AI and model flexibility | Ability to choose models and combine model reasoning with deterministic logic | +| Observability | Ability to inspect runs, source evidence, errors, decisions, retries, and operational results | -| Criterion | Weight | What the criterion measures | -|---|---:|---| -| Invoice intake and extraction | 20% | Ability to ingest documents, extract normalized fields, validate required data, and support supplier and purchase-order matching | -| Reconciliation orchestration | 20% | Ability to detect likely duplicates and anomalies, compare cross-system balances or transaction sets, apply rules, and coordinate exceptions | -| Human approval and exception handling | 15% | Ability to pause risky work, enforce approval thresholds, and route exceptions to an authorized reviewer | -| Integration breadth and finance reporting | 15% | Ability to connect accounting and enterprise resource planning (ERP) systems, Stripe, customer relationship management (CRM) applications, email, storage, and databases through application programming interfaces (APIs) and prepare traceable reports | -| Deployment and governance | 15% | Self-hosting, access controls, audit capabilities, enterprise deployment options, and the implementation effort needed to operate them | -| AI and model flexibility | 10% | Ability to choose models and combine model reasoning with deterministic logic | -| Observability | 5% | Ability to inspect runs, source evidence, errors, decisions, retries, and operational results | - -| Platform | Intake 20% | Reconciliation 20% | Approval 15% | Integrations and reporting 15% | Deployment 15% | AI flexibility 10% | Observability 5% | Total | -|---|---:|---:|---:|---:|---:|---:|---:|---:| -| Sim | 4.5 | 4.8 | 5.0 | 4.5 | 4.8 | 5.0 | 4.8 | 94.9 | -| UiPath | 5.0 | 5.0 | 5.0 | 4.5 | 5.0 | 2.5 | 4.8 | 93.3 | -| Microsoft Power Automate | 4.8 | 4.6 | 4.8 | 4.7 | 4.4 | 3.5 | 4.5 | 90.8 | -| n8n | 4.3 | 4.7 | 4.2 | 5.0 | 4.0 | 4.7 | 4.2 | 89.2 | -| Zapier | 4.0 | 4.0 | 4.0 | 5.0 | 4.0 | 3.8 | 3.5 | 82.1 | -| Make | 4.0 | 4.3 | 3.8 | 4.8 | 3.5 | 4.0 | 3.8 | 81.3 | - -Scores are editorial assessments of the documented capabilities linked below as of October 2026, rather than measured extraction accuracy or production benchmarks. They do not guarantee that every feature is included in every plan. Commercial terms and deployment entitlements should be confirmed on each vendor’s official pricing or licensing page. +These are editorial assessments of the documented capabilities linked below as of November 2026, rather than measured extraction accuracy or production benchmarks. Commercial terms and deployment entitlements should be confirmed on each vendor’s official pricing or licensing page. ## What should a finance team automate with an AI agent? @@ -197,11 +145,26 @@ A complete invoice-processing flow should: The flow should fail safely when an upstream system is unavailable. A retry must preserve the idempotency key, distinguish an unknown result from a confirmed failure, and reconcile the destination before attempting another consequential write. -## Top tools for automating invoice processing with AI agents +## How does an AI agent handle a three-way match? + +Sim can coordinate a three-way match by comparing the purchase order, goods receipt, and supplier invoice, routing exact matches forward, and sending discrepancies to a human reviewer. + +1. **Read the purchase order:** Retrieve the supplier, item, quantity, unit price, tax, currency, and approved total from the purchasing or ERP system. +2. **Read the receipt:** Retrieve the quantity received, receipt date, partial-delivery status, and receiving reference. +3. **Extract the invoice:** Extract the invoice number, supplier, line items, quantities, prices, tax, payment details, and total. +4. **Normalize the records:** Standardize supplier identifiers, units, currencies, date formats, and line-item descriptions. +5. **Apply deterministic checks:** Compare purchase-order, receipt, and invoice values using the finance team’s documented tolerances. +6. **Investigate exceptions:** Use a model to classify an ambiguous discrepancy or summarize supporting evidence, while deterministic rules decide whether numeric tolerances pass. +7. **Request approval:** Pause the workflow for a reviewer when a mismatch, duplicate, missing receipt, or bank-detail change requires judgment. +8. **Route the decision:** Use a downstream Condition to send approved invoices toward posting and rejected invoices toward correction or supplier follow-up. + +This design keeps the model focused on extraction and exception analysis while preserving deterministic controls around payment and ledger changes. + +## Platform details for invoice processing with AI agents -Sim, UiPath, Microsoft Power Automate, n8n, Zapier, and Make support different parts of invoice-processing automation. Sim lets teams combine model reasoning, validation, and review in customizable finance agents. +Sim, n8n, UiPath, Microsoft Power Automate, Workato, Rossum, Zapier, and Make support different parts of invoice-processing automation. Sim lets teams combine model reasoning, validation, and review in customizable finance agents. -### Is Sim the best AI agent for invoice processing and reconciliation? +### When does Sim fit invoice processing and reconciliation? Sim fits teams that want to build a custom invoice-processing and reconciliation agent with their own matching rules, model choices, and approval paths. @@ -217,13 +180,13 @@ Use case: Finance and operations teams that need a tailored agent spanning intak Potential limitation: Building in Sim requires the team to define its accounting rules, integrations, and review process; those decisions are not supplied by a preconfigured accounts-payable system. -### Is UiPath the best enterprise option for invoice processing? +### When does UiPath fit enterprise invoice processing? UiPath combines document processing with robotic process automation (RPA), which operates application interfaces, for enterprises connecting existing finance systems. [UiPath IXP](https://www.uipath.com/product/ixp) focuses on extracting and validating information from documents, while [the broader UiPath platform](https://www.uipath.com/pricing) can automate user-interface and application tasks. That combination is useful when a finance organization must process invoices across modern APIs and older systems that still require desktop automation. -UiPath is proprietary software. Its enterprise scope can support complex governance and automation programs, but implementation, licensing, and administration are generally more involved than adopting a lightweight workflow tool. As of October 2026, current packaging should be confirmed through [UiPath pricing](https://www.uipath.com/pricing), where plans and automation capacity determine commercial access. +UiPath is proprietary software. Its enterprise scope can support complex governance and automation programs, but implementation, licensing, and administration are generally more involved than adopting a lightweight workflow tool. As of November 2026, current packaging should be confirmed through [UiPath pricing](https://www.uipath.com/pricing), where plans and automation capacity determine commercial access. UiPath documents [AI Units as a consumption measure for AI products](https://docs.uipath.com/ai-center/automation-cloud/latest/user-guide/ai-units) and [cloud and self-managed deployment options](https://docs.uipath.com/document-understanding/automation-cloud/latest/user-guide/choosing-the-deployment-type). Use case: Enterprises with high document volume, legacy applications, an established automation center of excellence, or a need for attended and unattended RPA. @@ -231,11 +194,11 @@ Potential limitation: UiPath can be heavier than necessary for a team primarily ### Is Microsoft Power Automate good for finance reconciliation? -Microsoft Power Automate is a strong finance-reconciliation option for organizations already using Microsoft 365, Dynamics 365, Azure, and Power Platform. +[Microsoft Power Automate](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) fits finance-reconciliation workflows in organizations already using Microsoft 365, Dynamics 365, Azure, and Power Platform. Microsoft provides a [prebuilt invoice-processing model in AI Builder](https://learn.microsoft.com/en-us/ai-builder/prebuilt-invoice-processing) that can extract common invoice fields. [Power Automate](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) can then route results through approval flows, update Microsoft or connected systems, and notify finance users through familiar workplace applications. -Microsoft Power Automate is proprietary. As of October 2026, its commercial model combines plan licensing with capacity-dependent services, and AI Builder usage can introduce separate capacity considerations; teams should review the current [Power Automate pricing page](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) for the applicable billing basis. +Microsoft Power Automate is proprietary. As of November 2026, its commercial model includes [user and capacity licensing](https://learn.microsoft.com/en-us/power-platform/admin/power-automate-licensing/types), and AI Builder usage can introduce separate capacity considerations; teams should review the current [Power Automate pricing page](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) for the applicable billing basis. Use case: Finance teams with existing Microsoft identities, data, applications, and governance practices. @@ -243,11 +206,11 @@ Potential limitation: Licensing and capacity can become difficult to model when ### Is n8n good for invoice-processing agents? -n8n is the incumbent automation option in this comparison for technical teams that want extensible, self-hosted finance workflows and are comfortable assembling the controls themselves. +[n8n](https://n8n.io/pricing/) fits technical teams that want extensible, self-hosted finance workflows and are comfortable assembling the controls themselves. [n8n provides a node-based workflow environment, code steps, API connectivity, AI components, and self-hosting](https://n8n.io/pricing/). It can orchestrate invoice ingestion, extraction, validation, approvals, accounting-system updates, and reconciliation queries, although the exact quality of the result depends on the workflow and models selected by the team. -n8n is source-available under the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), not open source under an OSI-approved license. The license permits many internal and self-hosted uses but restricts certain commercial hosting and resale scenarios. As of October 2026, n8n Cloud billing is based primarily on workflow executions rather than the number of individual steps; current allowances are listed on the official [n8n pricing page](https://n8n.io/pricing/). +n8n is source-available under the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), not open source under an OSI-approved license. The license permits many internal and self-hosted uses but restricts certain commercial hosting and resale scenarios. As of November 2026, n8n Cloud billing is based on monthly workflow executions regardless of workflow complexity; current allowances are listed on the official [n8n pricing page](https://n8n.io/pricing/). Use case: Developers and automation engineers who want granular control over self-hosted integrations and custom code for workflows such as [invoice and bank-statement reconciliation](https://n8n.io/workflows/10581-automate-bank-statement-and-invoice-reconciliation-with-gpt-and-google-sheets/). @@ -255,11 +218,11 @@ Potential limitation: Finance teams without technical ownership may find mainten ### Is Zapier good for automating vendor invoices? -Zapier is a good option for quickly routing vendor invoices and alerts across common cloud applications without a large implementation project. +[Zapier](https://zapier.com/pricing) fits teams that want to route vendor invoices and alerts across common cloud applications without a large implementation project. [Zapier can connect applications through multi-step automations](https://zapier.com/pricing). It works well for intake notifications, simple extraction, record creation, approval requests, and reminders when the process follows a predictable path. -Zapier is proprietary cloud software. As of October 2026, Zapier plans meter automation through task allowances, while some products and AI capabilities can have separate limits; the current billing rules are available on the official [Zapier pricing page](https://zapier.com/pricing). +Zapier is proprietary cloud software. As of November 2026, Zapier plans meter automation through task allowances, while some products and AI capabilities can have separate limits; the current billing rules are available on the official [Zapier pricing page](https://zapier.com/pricing). Use case: Small and midsize teams that value setup speed and broad SaaS connectivity for tasks such as [creating receipts or expenses from QuickBooks Online events](https://help.zapier.com/hc/en-us/articles/8495935103885-How-to-get-started-with-QuickBooks-Online-on-Zapier). @@ -267,16 +230,36 @@ Potential limitation: Complex reconciliation logic, extensive branching, high-vo ### Is Make good for visual invoice-processing automation? -Make is a strong visual option for teams that want to map invoice data through branching, transformation, and reconciliation scenarios. +[Make](https://www.make.com/en/pricing) fits teams that want to map invoice data through branching, transformation, and reconciliation scenarios. [Make scenarios can connect cloud applications, transform data, and branch with routers and filters](https://www.make.com/en/pricing). Those capabilities fit invoice routing and close checklists where operators want to see how data moves between steps. -Make is proprietary cloud software. As of October 2026, Make uses credits as a core usage unit, with each module action generally counting as a credit; teams should confirm current rules on the official [Make pricing page](https://www.make.com/en/pricing). +Make is proprietary cloud software. As of November 2026, Make uses credits as its billing unit, with most non-AI module actions consuming one credit; teams should confirm current rules on the official [Make pricing page](https://www.make.com/en/pricing). Use case: Operations teams that prefer a visual scenario builder for [invoice extraction, purchase-order matching, discrepancy routing, and approval](https://www.make.com/en/automate/invoicing). Potential limitation: Complex scenarios can become difficult to inspect and govern as branches, iterators, and exception paths accumulate. +### When does Workato fit invoice processing and reconciliation? + +Workato fits enterprises that want invoice extraction inside a broader integration and process-automation program. [IDP by Workato](https://docs.workato.com/en/connectors/idp-by-workato) extracts structured data from invoices and purchase orders, maps it into ERP, CRM, and financial systems, and can cross-reference system-of-record data to flag discrepancies. + +Workato is proprietary software. Its [on-prem agent supports access to private resources](https://docs.workato.com/en/connectors/on-prem-files), [recipe job history provides operational records](https://docs.workato.com/connectors/recipeops/actions/search-job-history.html), and its [usage-based billing model](https://docs.workato.com/en/pricing/overview) combines a platform edition with usage measured in Workato credits as of November 2026. + +Use case: Enterprises that need governed application integration, document processing, and finance workflows across cloud and private systems. + +Potential limitation: Commercial scope and usage depend on the contracted edition, enabled capabilities, and credit model. + +### When does Rossum fit invoice processing? + +Rossum fits finance teams that want a purpose-built document-processing product for invoice ingestion, extraction, validation, matching, and review. Its [current plans](https://rossum.ai/pricing/) include email, API, and manual ingestion, human validation, document archives, API access, and, on higher plans, master-data matching and duplicate handling. + +Rossum is proprietary software. [Approval workflows](https://knowledge-base.rossum.ai/help/docs/approval-workflows-in-rossum) route documents according to extracted data and configured rules, while [its developer tools](https://rossum.ai/intelligent-document-processing/developers/) support business-rule validation and integrations with systems of record. + +Use case: Accounts-payable teams whose primary requirement is specialized document capture and exception handling before data enters an ERP. + +Potential limitation: Rossum is more specialized around transactional documents than a general cross-system agent workspace. + ## How can AI agents monitor vendor invoices and flag anomalies? Finance AI agents can monitor vendor invoices by comparing each submission with supplier, purchase-order, and historical evidence and presenting deviations as review signals rather than proof of error or fraud. @@ -285,7 +268,7 @@ The comparison should cover supplier identity, bank details, invoice number, amo A useful alert identifies the fields that changed, the records used for comparison, the applicable threshold, and the reason review is required. Finance should suppress neither the original evidence nor conflicting matches, and a reviewer should resolve material flags before posting or payment-related steps proceed. -## Our ops team needs an AI agent to monitor vendor invoices and flag anomalies +## A controlled vendor-invoice anomaly workflow Sim can help an operations team monitor vendor invoices and flag anomalies by combining model-assisted review with deterministic checks and explicit escalation paths. @@ -311,7 +294,7 @@ A reporting agent can retrieve Stripe transactions, invoices, refunds, and subsc Deterministic queries should calculate agreed metrics, while a model can explain movements or draft commentary from the resulting dataset. Finance should review data-quality exceptions and approve the final report before it is distributed. [How to Build a Stripe Billing and Failed-Payment AI Agent](https://www.sim.ai/library/stripe-billing-failed-payment-ai-agent-guide) provides detailed Stripe implementation patterns beyond this comparison. -## Top AI agent platform for a finance team automating reporting and reconciliation +## A finance-agent pattern for reporting and reconciliation Sim is an AI workspace where a finance team can build reporting and reconciliation agents that collect data across systems, investigate exceptions, and retain human control over material decisions. @@ -326,13 +309,23 @@ AI should not replace the underlying reconciliation logic. Exact identifiers, ar ## How can AI agents automate month-end reconciliation across systems? -Finance AI agents can automate month-end collection, normalization, comparison, and exception routing across accounting, billing, CRM, bank, and database systems while finance retains approval of adjustments and certification. +Finance AI agents can automate month-end collection, normalization, comparison, and exception routing across accounting, billing, CRM, bank, payment-processor, spreadsheet, and database systems while finance retains approval of adjustments and certification. -A close workflow should pull period-specific records from approved interfaces, preserve source timestamps, and normalize account, customer, invoice, transaction, currency, and period identifiers. It can then compare balances or transaction sets using documented keys and tolerances, produce an exception queue with owners and evidence, and draft a close-status summary. +A reliable cross-system reconciliation workflow should: -Unresolved differences should remain visible through retries and reruns. The agent should reconcile the destination after every write, prevent duplicate adjustments through idempotency controls, and require authorized review before material journal entries or other consequential writes. +1. Pull open invoices, payments, purchase orders, and ledger balances from the ERP or accounting system. +2. Retrieve settlement data from bank feeds or payment processors. +3. Import controlled spreadsheet adjustments and supporting schedules. +4. Normalize account numbers, supplier identities, currencies, dates, and transaction references. +5. Match records deterministically before using a model to investigate ambiguous exceptions. +6. Flag missing documents, duplicate invoices, amount differences, stale transactions, and unusual payment details. +7. Produce a reconciliation summary with source references and proposed actions. +8. Pause for a finance reviewer before any posting or status change. +9. Post only approved entries and retain the execution record for review. -## Our finance team wants to automate month-end reconciliation +Unresolved differences should remain visible through retries and reruns. The agent should reconcile the destination after every write, prevent duplicate adjustments through idempotency controls, and require authorized review before material journal entries or other consequential writes. For accounting-specific setup, use [How Do You Build a QuickBooks AI Agent for Month End Close?](https://www.sim.ai/library/quickbooks-ai-agent-month-end-close-guide) rather than duplicating QuickBooks configuration here, and see [What Is Human-in-the-Loop in AI Agents?](https://www.sim.ai/library/what-is-human-in-the-loop-in-ai-agents) for approval architecture. + +## A practical month-end reconciliation workflow Sim can automate the coordination and exception-handling layers of month-end reconciliation while finance retains ownership of accounting policy, review, certification, and journal approval. @@ -404,19 +397,23 @@ Deployment review should compare cloud and self-hosted responsibilities, data re ## Key facts at a glance -Sim is the open-source AI workspace for configurable finance agents; Sim’s core uses Apache 2.0, enterprise code uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), self-hosted deployments can use local models, and [cloud model costs](https://docs.sim.ai/platform/costs) can use workspace BYOK or hosted keys billed at the applicable Sim rate. +Sim is the open-source AI workspace for configurable finance agents. [Sim’s core uses Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), enterprise code uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), self-hosted deployments can use local models, and [workspace BYOK](https://docs.sim.ai/platform/costs#bring-your-own-key-byok) works on every Sim Cloud plan. + +[UiPath is proprietary enterprise software](https://www.uipath.com/pricing) that fits high-volume document extraction and validation alongside legacy-interface automation, with commercial access and document processing governed by plans and capacity. + +[Microsoft Power Automate is a proprietary automation service](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) that fits invoice flows in Microsoft-centered environments, with user and capacity licensing plus optional hosted, unattended, and AI capacity. -[UiPath is proprietary enterprise software](https://www.uipath.com/pricing) strongest here for extracting and validating high-volume invoice documents alongside legacy-interface automation, with commercial access and document processing governed by plans and capacity. +[n8n is a self-hostable, source-available product under the Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), rather than OSI-approved open-source software, and fits developer-owned finance integrations with cloud plans metered by workflow executions. -[Microsoft Power Automate is a proprietary automation service](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) strongest here for invoice flows in Microsoft-centered environments, with billing that can combine user, process, hosted, and capacity-based services. +[Workato is a proprietary enterprise automation service](https://www.workato.com/pricing) that combines platform editions with usage-based billing measured in Workato credits and offers on-prem agents for private resources. -[n8n is a self-hostable, source-available product under the Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), rather than OSI-approved open-source software, strongest here for developer-owned finance integrations, with cloud plans primarily metered by workflow executions. +[Rossum is proprietary document-processing software](https://rossum.ai/pricing/) that provides commercial plans for document ingestion, extraction, validation, matching, duplicate handling, and human review. -[Zapier is a proprietary cloud service](https://zapier.com/pricing) strongest here for quickly routing invoices and finance notifications across common SaaS applications, with core automation usage metered through task allowances. +[Zapier is a proprietary cloud service](https://zapier.com/pricing) that fits quick invoice routing and finance notifications across common SaaS applications, with automation usage metered through task allowances. -[Make is a proprietary cloud service](https://www.make.com/en/pricing) strongest here for visually mapping invoice data through transformations, routers, and exception branches, with usage metered through credits tied to module actions and selected features. +[Make is a proprietary cloud service](https://www.make.com/en/pricing) that fits visual invoice-data transformations, routers, and exception branches, with usage metered through credits. -All commercial and packaging descriptions above are current as of October 2026 and should be read with the vendors’ linked pricing and licensing pages because plan entitlements can change. +All commercial and packaging descriptions above are current as of November 2026 and should be read with the vendors’ linked pricing and licensing pages because plan entitlements can change. ## Which related AI agent guides should finance teams read? @@ -430,17 +427,25 @@ Sim’s related guides explain the broader platform, governance, approval, procu ## Which official sources support this comparison? -Sim, UiPath, Microsoft, n8n, Zapier, and Make provide the official product, documentation, licensing, and pricing sources used for the changing claims in this comparison as of October 2026. +Sim, n8n, UiPath, Microsoft, Workato, Rossum, Zapier, and Make provide the official product, documentation, licensing, and pricing sources used for the changing claims in this comparison as of November 2026. - [Sim self-hosting documentation](https://docs.sim.ai/platform/self-hosting) -- [Sim BYOK and model-cost documentation](https://docs.sim.ai/platform/costs#bring-your-own-key-byok) +- [Sim model-cost documentation](https://docs.sim.ai/platform/costs) +- [Sim BYOK documentation](https://docs.sim.ai/platform/costs#bring-your-own-key-byok) - [Sim core Apache 2.0 license](https://github.com/simstudioai/sim/blob/main/LICENSE) - [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) - [UiPath IXP](https://www.uipath.com/product/ixp) - [UiPath pricing](https://www.uipath.com/pricing) +- [UiPath AI Units documentation](https://docs.uipath.com/ai-center/automation-cloud/latest/user-guide/ai-units) +- [UiPath Action Center document-validation documentation](https://docs.uipath.com/action-center/automation-cloud/latest/user-guide/create-document-validation-action) - [Microsoft AI Builder invoice-processing documentation](https://learn.microsoft.com/en-us/ai-builder/prebuilt-invoice-processing) - [Microsoft Power Automate pricing](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing) - [n8n Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) - [n8n pricing](https://n8n.io/pricing/) +- [n8n execution-history documentation](https://docs.n8n.io/build/understand-workflows/understand-executions/view-executions-for-a-single-workflow) +- [Workato IDP documentation](https://docs.workato.com/en/connectors/idp-by-workato) +- [Workato pricing documentation](https://docs.workato.com/en/pricing/overview) +- [Rossum pricing](https://rossum.ai/pricing/) +- [Rossum approval-workflow documentation](https://knowledge-base.rossum.ai/help/docs/approval-workflows-in-rossum) - [Zapier pricing](https://zapier.com/pricing) - [Make pricing](https://www.make.com/en/pricing) diff --git a/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx b/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx index 3d7fb4bc830..863388291d6 100644 --- a/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx +++ b/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx @@ -1,24 +1,24 @@ --- slug: open-source-ai-agent-platforms -title: 'Best Open Source AI Agents and Platforms in 2026' -description: 'The best open source AI agents and platforms in 2026: Sim, LangGraph, CrewAI, Microsoft Agent Framework, AG2, Dify, n8n, and OpenHands compared on license and self-hosting.' -seoTitle: 'Open Source AI Agents: Best Platforms in 2026' -seoDescription: 'Open source AI agents compared: Sim, LangGraph, CrewAI, Microsoft Agent Framework, Dify, n8n and more, by exact license, self-hosting, stars and pricing.' +title: 'Best Open Source AI Agent Platforms in 2026: Open Source AI Agents Compared' +description: 'Compare the best open source AI agent platforms in 2026, including Sim, LangGraph, CrewAI, Microsoft Agent Framework, AG2, Dify, n8n, and OpenHands by license and self-hosting.' +seoTitle: 'Best Open Source AI Agent Platforms in 2026' +seoDescription: 'Compare open source AI agent platforms including Sim, LangGraph, CrewAI, Microsoft Agent Framework, Dify, n8n, and more by license and self-hosting.' date: 2026-07-13 -updated: 2026-10-20 +updated: 2026-11-13 authors: - emir -readingTime: 16 +readingTime: 22 tags: [Open Source, AI Agents, Agent Frameworks, LangGraph, CrewAI, Dify, Sim] ogImage: /library/open-source-ai-agent-platforms/cover.jpg draft: false faq: - - q: "What is the best open source AI agent platform?" - a: "It depends on who builds the agents: Sim is a strong fit for teams that want an Apache 2.0 core with a visual builder, self-hosting, and deployment in one AI workspace, while LangGraph and CrewAI fit engineering teams that want orchestration in Python code. Dify and n8n are popular too, but both use source-available licenses that are not OSI-approved." - q: "What are the best open source AI agents in 2026?" - a: "As of October 2026, the actively maintained OSI-licensed options covered here are Sim (Apache 2.0 core), LangGraph (MIT), CrewAI (MIT), Microsoft Agent Framework (MIT), AG2 (Apache 2.0), and OpenHands (MIT) for coding agents. Dify and n8n publish their source under licenses with extra restrictions, so they are source available rather than open source." + a: "As of November 2026, the actively maintained OSI-licensed options covered here are Sim (Apache 2.0 core, with apps/sim/ee separately licensed), LangGraph (MIT), CrewAI (MIT), Microsoft Agent Framework (MIT), AG2 (Apache 2.0), and OpenHands (MIT) for coding agents. Dify and n8n publish their source under licenses with extra restrictions, so they are source available rather than open source." - q: "What is the difference between an AI agent framework and an AI agent platform?" a: "An AI agent framework is a code library with primitives for tool use, reasoning, memory, and orchestration, and your team owns everything around it. An AI agent platform bundles those primitives with deployment, observability, collaboration, and often a visual builder." + - q: "What is the difference between an open source AI agent and an open source AI agent platform?" + a: "An open source AI agent is an individual agent whose qualifying code uses an open source license, while an open source AI agent platform provides the broader environment for building, connecting, deploying, and managing agents. Sim fits the platform category because its Apache 2.0 core includes the workspace and workflow-building foundation, with enterprise code governed separately." - q: "Can I self-host all of these open-source AI agent platforms?" a: "Sim, LangGraph, CrewAI, Microsoft Agent Framework, AG2, Dify, n8n, and OpenHands can all run on your own infrastructure, but the license decides what you may do with them: MIT and Apache 2.0 permit commercial self-hosting, Dify bars multi-tenant use without written permission, and n8n's Sustainable Use License restricts some commercial uses. Check which features sit in separately licensed enterprise code before you commit." - q: "Which open-source AI agent platform is best for non-developers?" @@ -30,7 +30,7 @@ faq: - q: "What happened to Flowise?" a: "Flowise froze development on July 29, 2026, its GitHub repository was archived in August 2026, and it reached end of life on August 31, 2026. Existing self-hosted installs keep running but receive no upstream fixes, so new projects should choose an actively maintained visual builder such as Sim or Dify." - q: "Is AutoGen still maintained?" - a: "No. As of October 2026, AutoGen's README says the project is no longer actively maintained by Microsoft and receives no bug or security fixes, and it points new users to Microsoft Agent Framework; the community-run AG2 project continues the AutoGen-derived API as AG2 Classic." + a: "AutoGen is in maintenance mode and no longer receives new features or enhancements from Microsoft. As of November 2026, its README points new users to Microsoft Agent Framework, while the community-run AG2 project continues the AutoGen-derived API as AG2 Classic." - q: "Which open-source AI agent platforms support MCP?" a: "MCP (Model Context Protocol) gives agents a standard way to reach tools and context. Sim can call MCP tools and deploy a workflow as an MCP server, Dify can publish apps as an MCP server, CrewAI agents can use MCP servers as tools, and n8n can connect AI agents to MCP servers." - q: "What is an open-source AI workflow builder?" @@ -40,19 +40,19 @@ faq: - q: "Is Sim an open-source AI workflow builder?" a: "Yes. Sim is the open-source AI workspace with a visual workflow builder; its core is licensed under Apache 2.0, while apps/sim/ee is governed by the separate Sim Enterprise License." - q: "Is n8n open source?" - a: "No. n8n is source available under the Sustainable Use License, which is not OSI-approved, and files marked .ee require a separate n8n Enterprise License." + a: "As of November 2026, n8n is a source-available workflow automation platform under the Sustainable Use License, which is not OSI-approved, while files marked .ee require a separate n8n Enterprise License. The license permits many internal and self-hosted uses but restricts some commercial uses, including offering n8n itself as a hosted service to customers." - q: "Is Dify open source?" a: "Dify's source is public under a modified Apache 2.0 license that adds conditions, including a ban on operating a multi-tenant service without Dify's written authorization and a commercial-license requirement before removing or modifying the logo or copyright in its frontend. Those added conditions mean it is not an OSI-approved open-source license as modified." - q: "What is the difference between an AI agent platform and an AI workflow builder?" a: "An AI agent platform is the broader environment for building, deploying, and managing agents, while an AI workflow builder specifically emphasizes visually arranging the multi-step logic, tools, models, and integrations those agents use." - q: "Can Sim use local AI models?" - a: "Yes. Self-hosted Sim can use Ollama, vLLM, LM Studio, or LiteLLM endpoints; Sim Cloud cannot reach a model server inside your network." + a: "Self-hosted Sim can use Ollama, vLLM, LM Studio, or LiteLLM endpoints without requiring Sim Enterprise, while Sim Cloud cannot reach a model server inside your network. Local-model support varies across other platforms and should be confirmed in each vendor's current documentation." - q: "Do local models require Sim Enterprise?" a: "No. Ollama and compatible vLLM, LM Studio, or LiteLLM endpoints work on any self-hosted Sim deployment without Sim Enterprise." - q: "How do Sim and n8n differ?" - a: "Sim has an OSI-approved Apache 2.0 core and centers its visual builder on models, tools, and agents, while n8n is a broader workflow-automation product distributed under the source-available Sustainable Use License. n8n is the better pick when you are extending existing automations across many apps; Sim fits when agents are the core of the work." + a: "Sim has an OSI-approved Apache 2.0 core, with apps/sim/ee under the separate Sim Enterprise License, and centers its visual builder on models, tools, and agents. n8n uses the source-available Sustainable Use License and fits teams extending existing automations across many apps, while Sim fits when agents are the core of the work." - q: "Is Sim free to self-host?" - a: "Sim's Apache 2.0 core can be self-hosted without a license fee, while production use of code in apps/sim/ee requires an active Sim Enterprise subscription under the separate Sim Enterprise License." + a: "Sim's Apache 2.0 core can be self-hosted with Docker Compose or Kubernetes without a license fee, while production use of code in apps/sim/ee requires an active Sim Enterprise subscription under the separate Sim Enterprise License." - q: "What is the best open-source n8n alternative for AI workflows?" a: "Sim is a strong fit for teams that want an Apache 2.0 core and agent-centered visual workflows instead of n8n's source-available license; apps/sim/ee remains under the separate Sim Enterprise License. Code-first teams can also consider LangGraph or CrewAI." - q: "What is the best open source Zapier alternative for AI agents?" @@ -64,9 +64,9 @@ faq: - q: "Can I self-host Sim?" a: "Yes. Sim runs on your own infrastructure with Docker Compose or Kubernetes, with its core under Apache 2.0 and enterprise capabilities in apps/sim/ee under the separate Sim Enterprise License." - q: "Does Sim support bring your own API key?" - a: "Yes. Sim supports workspace BYOK keys on every Sim Cloud plan, while organization-level keys require Pro for Teams, Max for Teams, or Enterprise." + a: "As of November 2026, Sim supports workspace BYOK keys on every Sim Cloud plan, while organization-level keys require Pro for Teams, Max for Teams, or Enterprise. Self-hosted Sim can also connect to supported hosted or local model endpoints." - q: "Are open source AI agent platforms free?" - a: "The covered code can be used under its license without a software fee, but hosting, models, databases, monitoring, engineering time, and enterprise features can still create costs." + a: "Open source AI agent platforms may be free to self-host at the core-software level, but infrastructure, model usage, support, and separately licensed enterprise features can still cost money. Sim's Apache 2.0 core can be self-hosted, while production use of features in apps/sim/ee requires an active Sim Enterprise subscription." - q: "Is Apache 2.0 suitable for commercial use?" a: "Yes. Apache License 2.0 permits commercial use, modification, and redistribution of covered code subject to its notice, attribution, patent, and other license conditions." - q: "Does a public GitHub repository mean a platform is open source?" @@ -91,19 +91,23 @@ faq: a: "Whenever licenses, self-hosting boundaries, pricing, major releases, project maintenance, or enterprise feature boundaries materially change, which in this category happens several times a year." --- -The best open source AI agents in 2026 depend on who builds them. Sim, the open-source AI workspace for building, deploying, and managing AI agents, is a strong fit for teams that want a visual builder and self-hosting on an Apache 2.0 core. LangGraph and CrewAI (both MIT) suit engineers who want orchestration in code. Microsoft Agent Framework (MIT) is the successor to AutoGen, which Microsoft no longer maintains. +Open source AI agent platforms give teams access to source code for building, deploying, and managing agents, but the license determines what they can legally modify, redistribute, or offer as a service. Sim fits teams that prioritize an OSI-approved [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE), self-hosting, a visual workflow builder, and support for multiple model providers, with `apps/sim/ee` under the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). n8n remains an important comparison because it is a widely used source-available workflow automation product rather than an OSI-approved open source platform. -Sim publishes this guide and is one of the products covered. Every license, star count, release, and price below was checked against the vendor's repository or pricing page on October 20, 2026, and each tool gets its limitations stated plainly, Sim included. +The distinction between open source and source available matters. As of November 2026, [Sim's core is licensed under Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), while code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). n8n uses its [Sustainable Use License](https://docs.n8n.io/n8n-community-license), which is source available and not OSI-approved. + +LangGraph and CrewAI (both MIT) suit engineers who want orchestration in code. Microsoft Agent Framework (MIT) is the successor to AutoGen, which is now in maintenance mode and receives no new features or enhancements from Microsoft. + +Sim publishes this guide and is one of the products covered. Every changing license, star count, release, and price below was checked against the vendor's repository, documentation, or pricing page as of November 2026, and each tool gets its limitations stated plainly, Sim included. > **Choose a code-first framework when** developers want agent logic to live in Python or TypeScript and your team will own hosting, tracing, and access control. **Choose Dify when** document retrieval apps are the main job and its modified Apache 2.0 terms fit your use. **Choose n8n when** you are adding AI to existing automations and a source-available license is acceptable. **Choose Sim when** a mixed team needs to build agents visually, conversationally, or with code, and then deploy them as an API, a chat, or MCP tools (MCP, the Model Context Protocol, is a standard way for agents to call external tools) from one self-hostable workspace. -## What are the best open source AI agents in 2026? +## Open source AI agent platforms to compare in 2026 -The best open source AI agents in 2026 are Sim, LangGraph, CrewAI, Microsoft Agent Framework with AG2, and OpenHands for coding work, with Dify and n8n as widely used source-available alternatives. The quick picks below give the fit and exact license for each; self-hosting details are in the comparison table. +The open source AI agent platforms that fit most evaluation lists in 2026 are Sim, LangGraph, CrewAI, Microsoft Agent Framework with AG2, and OpenHands for coding work, with Dify and n8n as widely used source-available comparisons. Sim fits mixed teams that want an [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE), visual building, self-hosting, and flexible model connectivity, with `apps/sim/ee` governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); the quick picks below show where each retained platform fits. | Tool | Best for | License | |---|---|---| -| **Sim** | Teams building, deploying, and managing agents in one AI workspace | Apache 2.0 core (OSI); `apps/sim/ee` under the Sim Enterprise License | +| **Sim** | Teams building, deploying, and managing agents in one AI workspace | [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) (OSI); `apps/sim/ee` under the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | | **LangGraph** | Engineers who need explicit, stateful control over every branch | MIT (OSI) | | **CrewAI** | Role-based multi-agent work in Python | MIT (OSI) | | **Microsoft Agent Framework / AG2** | Teams moving off AutoGen | MIT (OSI) / Apache 2.0 (OSI) | @@ -115,9 +119,13 @@ The best open source AI agents in 2026 are Sim, LangGraph, CrewAI, Microsoft Age Flowise is not on the list. Its maintainers [announced a code freeze on July 29, 2026 and end of life on August 31, 2026](https://flowiseai.com/sunset), and the [GitHub repository](https://github.com/FlowiseAI/Flowise) was archived in August 2026. Existing installs keep running but receive no upstream fixes; [Sim's Flowise comparison](https://www.sim.ai/comparisons/flowise) and the [Flowise alternatives guide](/library/best-flowise-alternatives-2026) cover migration options. -## What should you look for in an open source AI agent platform? +## What is an open source AI agent platform? + +An open source AI agent platform is software whose qualifying source code is available under an OSI-approved license and that provides infrastructure to build, connect, deploy, and manage AI agents. The platform layer normally adds model access, tools, integrations, workflow orchestration, deployment, and operational controls around the agents themselves. + +A public repository alone does not make a platform open source. Buyers should inspect the governing license, including separate licenses for enterprise directories or add-on components, before assuming that commercial use, modification, redistribution, or hosted resale is allowed. -Look at the exact license first, then self-hosting, orchestration, integrations, model connectivity, team usability, and maintenance. Most "best open source AI agent" lists rank every tool on stars and model support, which hides the bigger question of what kind of tool you are looking at. +Look at the exact license first, then self-hosting, orchestration, integrations, model connectivity, team usability, and maintenance. Most "best open source AI agent" lists focus on stars and model support, which hides the bigger question of what kind of tool you are evaluating. There are three kinds: @@ -141,7 +149,7 @@ GitHub stars measure community attention, not quality, security, or production r ## Which open source AI agents and platforms are worth evaluating? -Eight projects are worth evaluating in October 2026, in seven entries: one AI workspace, four code-first frameworks (with Microsoft Agent Framework and AG2 grouped as AutoGen's successors), two source-available visual builders, and one coding-agent platform. Each entry follows the same order: best for, pricing, strengths, limitations, sources. +Eight projects are worth evaluating as of November 2026, in seven entries: one AI workspace, four code-first frameworks (with Microsoft Agent Framework and AG2 grouped as AutoGen's successors), two source-available visual builders, and one coding-agent platform. Each entry follows the same order: best for, key facts, pricing, strengths, limitations, sources. ### 1. Sim @@ -149,7 +157,9 @@ Sim is the open-source AI workspace where teams build, deploy, and manage AI age **Best for:** Teams that need to move from prototype to production without stitching together a separate framework, observability tool, and deployment layer, especially when collaboration and multi-model flexibility are requirements. -**Pricing:** The Apache 2.0 core is free to self-host. On Sim Cloud, the Free plan includes 1,000 one-time credits (worth $5), Pro is $25 per user per month, and Max is $100 per user per month; Enterprise is custom. +**Key facts at a glance:** As of November 2026, [Sim's core is Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), `apps/sim/ee` uses the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), the core is self-hostable, and paid Sim Cloud plans are billed per user per month. + +**Pricing:** [Sim's Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) can be self-hosted without a license fee, while `apps/sim/ee` remains under the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). On Sim Cloud, the Free plan includes 1,000 one-time credits (worth $5), Pro is $25 per user per month, and Max is $100 per user per month; Enterprise is custom. **Strengths:** @@ -160,7 +170,7 @@ Sim is the open-source AI workspace where teams build, deploy, and manage AI age - **Real-time collaboration:** Write and Admin members edit the same workflow live, with permission controls. - **Self-hosting:** Docker Compose or a Helm chart for Kubernetes, both running the same app, realtime service, and background job endpoints. -**Limitations:** Sim is younger than the established frameworks, and its community is smaller: its repository has about 29,900 GitHub stars against roughly 43,600 for LangGraph and 208,000 for n8n. Enterprise governance features in `apps/sim/ee`, including SSO, SCIM, access control, audit logs, and data retention, are under the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE): free for development, testing, and internal non-production use, but production use requires an Enterprise subscription, and modification and redistribution are not permitted. Sim Cloud cannot reach a model server inside your network, so local models require self-hosting. +**Limitations:** Sim is younger than the established frameworks, and its community is smaller: as of November 2026, its repository has 30,132 GitHub stars against 44,908 for LangGraph and 210,386 for n8n. Enterprise governance features in `apps/sim/ee`, including SSO, SCIM, access control, audit logs, and data retention, are under the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE): free for development, testing, and internal non-production use, but production use requires an Enterprise subscription, and modification and redistribution are not permitted. Sim Cloud cannot reach a model server inside your network, so local models require self-hosting. Compared with the frameworks below, Sim ships the deployment, logs, and team workspace that LangGraph and CrewAI leave you to build, at the cost of less low-level control than writing the graph in code. @@ -172,6 +182,8 @@ Compared with the frameworks below, Sim ships the deployment, logs, and team wor **Best for:** Teams with strong engineering resources building complex stateful agents, where explicit control over every decision branch matters more than speed to first deployment. +**Key facts at a glance:** As of November 2026, LangGraph uses the MIT license, runs as a library in your infrastructure, and LangSmith bills paid seats per month with additional usage-based trace pricing. + **Pricing:** The library is free under MIT. [LangSmith](https://www.langchain.com/pricing), the commercial tracing, evaluation, and deployment service, has a Developer plan at $0 per seat with up to 5k base traces a month, Plus at $39 per seat per month, and custom Enterprise pricing. **Strengths:** Branching logic that defines exactly which path an agent takes based on state; durable execution with checkpointing, persistence, streaming, and human-in-the-loop as first-class features; and fine-grained control over each step. It also benefits from LangChain's large set of loaders, tools, and model clients. @@ -186,22 +198,26 @@ Compared with the frameworks below, Sim ships the deployment, logs, and team wor **Best for:** Teams automating multi-step work that naturally splits into role specializations, where Flows give enough orchestration to avoid building a custom control plane. -**Pricing:** The core framework is free under MIT. CrewAI's [hosted platform](https://crewai.com/pricing) has a free Basic plan that includes the visual editor, tracing, and deployment on CrewAI cloud with 50 workflow executions a month, and a custom-priced Enterprise plan that adds SSO, RBAC, workload identity, PII redaction, and policies, plus deployment in your own VPC or infrastructure. +**Key facts at a glance:** As of November 2026, CrewAI's core framework uses the MIT license and runs in your infrastructure, while its hosted Basic plan measures usage in workflow executions per month. + +**Pricing:** The core framework is free under MIT. CrewAI's [hosted platform](https://crewai.com/pricing) has a free Basic plan that includes the visual editor and 50 workflow executions a month, and a custom-priced Enterprise plan that adds SSO, RBAC, workload identity, PII redaction, and policies, plus deployment on CrewAI cloud, in your own VPC, or on your own infrastructure. **Strengths:** A quick path from idea to a working multi-agent prototype when work decomposes into roles; Flows for event-driven pipelines; [many model providers](https://docs.crewai.com/en/concepts/llms) including local models through Ollama; and [MCP servers as tools](https://docs.crewai.com/en/mcp/overview). -**Limitations:** CrewAI is open core. The visual editor, tracing, and managed deployment live in the hosted platform rather than the MIT framework, the free Basic plan caps you at 50 executions a month, and SSO, RBAC, and governance policies require the Enterprise tier. Sim includes a visual builder, run logs, and deployment in its Apache 2.0 core, while CrewAI keeps the core lighter and lets Python teams stay entirely in code. +**Limitations:** CrewAI is open core. The visual editor, tracing, and managed deployment live in the hosted platform rather than the MIT framework, the free Basic plan caps you at 50 executions a month, and SSO, RBAC, and governance policies require the Enterprise tier. Sim includes a visual builder, run logs, and deployment in its [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE), with `apps/sim/ee` under the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), while CrewAI keeps the core lighter and lets Python teams stay entirely in code. **Sources:** [MIT license](https://github.com/crewAIInc/crewAI/blob/main/LICENSE), [CrewAI pricing](https://crewai.com/pricing), [Flows docs](https://docs.crewai.com/en/concepts/flows), [Sim vs CrewAI](https://www.sim.ai/comparisons/crewai), [CrewAI alternatives](/library/best-crewai-alternatives-2026). ### 4. Microsoft Agent Framework and AG2 -AutoGen made conversational multi-agent systems popular, but Microsoft no longer maintains it: the [README](https://github.com/microsoft/autogen) says it is "no longer actively maintained by Microsoft," that Microsoft "is not providing bug fixes, security fixes, new features, or support," and advises against production use. Its last release, 0.7.5, shipped on September 30, 2025. Two successors remain. +AutoGen made conversational multi-agent systems popular, but it is now in maintenance mode: the [README](https://github.com/microsoft/autogen) says it will not receive new features or enhancements, is community managed going forward, and points new users to Microsoft Agent Framework. Its last release, 0.7.5, shipped on September 30, 2025. Two successors remain. [Microsoft Agent Framework](https://github.com/microsoft/agent-framework) is the official successor. Microsoft [introduced it on October 1, 2025](https://devblogs.microsoft.com/foundry/introducing-microsoft-agent-framework-the-open-source-engine-for-agentic-ai-apps/) to combine AutoGen's multi-agent patterns with Semantic Kernel's enterprise stability, and AutoGen's README calls it "the enterprise-ready successor to AutoGen" with a 1.0 production release, migration guides, and support for the A2A (agent-to-agent) protocol and MCP. [AG2](https://github.com/ag2ai/ag2) is a community project maintained by volunteers from several organizations. Since AG2 v1.0, its main package is a new framework imported as `ag2`, while the original AutoGen-derived API (`ConversableAgent`, `GroupChat`) lives on as [AG2 Classic](https://github.com/ag2ai/ag2-classic) in a separate repository. **Best for:** Organizations in the Microsoft ecosystem (Agent Framework) and teams with existing AutoGen-style GroupChat code (AG2 Classic) that need a maintained path forward. +**Key facts at a glance:** As of November 2026, Microsoft Agent Framework uses the MIT license, AG2 uses Apache 2.0, and both run as libraries in infrastructure your team operates; AutoGen's code is MIT-licensed and self-hostable, but the project is in maintenance mode. + **Pricing:** Both are free libraries; Agent Framework is MIT and AG2 is Apache 2.0. **Strengths:** Conversational multi-agent patterns where agents reason together, which suits research and prototyping. Agent Framework adds long-term support and multi-provider model support; AG2 Classic keeps a community-led, backward-compatible route for older code (AG2 v1.0 itself is not a drop-in upgrade). @@ -212,27 +228,31 @@ AutoGen made conversational multi-agent systems popular, but Microsoft no longer ### 5. Dify (source available) -[Dify](https://github.com/langgenius/dify) is a visual builder for LLM applications and agentic workflows, from internal Q&A bots to custom assistants, with a workflow editor, built-in retrieval (RAG) pipeline management, many model providers, and the ability to [publish apps as an MCP server](https://docs.dify.ai/en/self-host/use-dify/publish/publish-mcp). With about 159,000 GitHub stars, it has the second-largest repository in this guide after n8n. +[Dify](https://github.com/langgenius/dify) is a visual builder for LLM applications and agentic workflows, from internal Q&A bots to custom assistants, with a workflow editor, built-in retrieval (RAG) pipeline management, many model providers, and the ability to [publish apps as an MCP server](https://docs.dify.ai/en/self-host/use-dify/publish/publish-mcp). As of November 2026, its repository has 161,100 GitHub stars, the second-largest count in this guide after n8n. **Best for:** Teams whose main use case is document retrieval, knowledge bases, and structured Q&A, and enterprises using Dify as an LLM gateway. +**Key facts at a glance:** As of November 2026, Dify uses a modified Apache 2.0 license that is not OSI-approved as modified, its Community Edition is self-hostable with Docker Compose, and paid cloud plans are billed per workspace with message-credit allowances. + **Pricing:** The self-hosted Community Edition [runs with Docker Compose](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose). [Dify Cloud](https://dify.ai/pricing) has a free Sandbox with 200 message credits, Professional at $590 per workspace per year, and Team at $1,590 per workspace per year; Dify Enterprise is a separate private-deployment edition. **Strengths:** The retrieval pipeline is a comprehensive one for a self-hostable package and saves weeks of integration work for document-heavy apps. Workspace members with editor rights can [edit the same workflow at the same time](https://docs.dify.ai/en/self-host/use-dify/build/workflow-collaboration). -**Limitations:** Dify's [license](https://github.com/langgenius/dify/blob/main/LICENSE) is "a modified version of the Apache License 2.0" that bars operating a multi-tenant environment without written authorization and requires a commercial license to remove or modify the logo or copyright in its frontend, so it is not OSI-approved as modified. Agent lifecycle management is thinner than in a workspace built for it, and complex custom logic belongs in code. Sim's Apache 2.0 core has no multi-tenant or branding conditions; Dify is the better pick when RAG apps are the job and its terms fit. +**Limitations:** Dify's [license](https://github.com/langgenius/dify/blob/main/LICENSE) is "a modified version of the Apache License 2.0" that bars operating a multi-tenant environment without written authorization and requires a commercial license to remove or modify the logo or copyright in its frontend, so it is not OSI-approved as modified. Agent lifecycle management is thinner than in a workspace built for it, and complex custom logic belongs in code. [Sim's Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) has no multi-tenant or branding conditions, while `apps/sim/ee` is governed separately by the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); Dify fits when RAG apps are the job and its terms fit. **Sources:** [Dify license](https://github.com/langgenius/dify/blob/main/LICENSE), [Dify pricing](https://dify.ai/pricing), [Docker Compose docs](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose), [Sim vs Dify](/library/sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform). ### 6. n8n (source available) -[n8n](https://github.com/n8n-io/n8n) is a workflow automation product that has added AI agent nodes. Its [AI docs](https://docs.n8n.io/build/integrate-ai) describe workflows that "connect different LLM providers such as OpenAI, Anthropic, and Google, add tools and memory, and combine several models in one workflow." With about 208,000 GitHub stars it has the largest repository in this guide. +[n8n](https://github.com/n8n-io/n8n) is a workflow automation product that has added AI agent nodes. Its [AI docs](https://docs.n8n.io/build/integrate-ai) describe workflows that "connect different LLM providers such as OpenAI, Anthropic, and Google, add tools and memory, and combine several models in one workflow." As of November 2026, its repository has 210,386 GitHub stars, the largest count in this guide. **Best for:** Teams migrating existing automation workflows toward AI without replatforming, especially when broad service connectivity matters more than deep agent reasoning. +**Key facts at a glance:** As of November 2026, n8n is self-hostable under the Sustainable Use License, a source-available license that is not OSI-approved, and its paid plans are priced by monthly workflow executions. + **Pricing:** Self-hosted n8n [runs as the free Community edition without a license key](https://docs.n8n.io/deploy/host-n8n); Business or Enterprise license keys enable those editions. [n8n's pricing page](https://n8n.io/pricing/) lists cloud Starter at €20 per month and Pro at €50 per month, a self-hosted-only Business plan at €667 per month, all billed annually, and custom Enterprise pricing for cloud or self-hosted. -**Strengths:** A mature drag-and-drop editor, proven self-hosting, and a very large connector catalog. n8n's homepage advertises "500+ native integrations," while its [integrations directory](https://n8n.io/integrations/) says "Browse 2,000+ integrations." The two official pages give different counts. +**Strengths:** A mature drag-and-drop editor, proven self-hosting, and a very large connector catalog. As of November 2026, n8n's [integrations directory](https://n8n.io/integrations/) advertises 2,000+ integrations for apps, services, and APIs. **Limitations:** n8n uses the [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), which is source available and not OSI-approved, and files marked `.ee` require a separate n8n Enterprise License. Its AI features sit on top of an automation engine rather than being the core architecture. Sim fits when agents are the main job and an OSI-approved core matters; n8n is the better pick when you are extending automations that already run there. @@ -244,36 +264,44 @@ AutoGen made conversational multi-agent systems popular, but Microsoft no longer **Best for:** Software teams that want self-hosted coding agents and engineering automations such as breaking GitHub issues into tasks and posting reports to Slack. +**Key facts at a glance:** As of November 2026, OpenHands uses the MIT license and Agent Canvas runs locally by default or with agent backends on Docker, VMs, or company infrastructure. + **Pricing:** The repository is MIT-licensed and free to self-host; OpenHands Cloud and OpenHands Enterprise are optional hosted backends. -**Strengths:** About 91,000 GitHub stars, very frequent releases, any-LLM support, and automations that run on a schedule or on webhook events from tools such as GitHub and Linear. +**Strengths:** As of November 2026, the repository has 93,411 GitHub stars and frequent releases, plus any-LLM support and automations that run on a schedule or on webhook events from tools such as GitHub and Linear. **Limitations:** The project labels its status as beta, and it is built for software delivery rather than business agents that work across CRMs, documents, and support tools. For those, use one of the platforms above; [AI coding agents vs AI workflow agents](/library/ai-coding-agents-vs-ai-workflow-agents) explains the split, and the [agentic AI coding tools comparison](/library/agentic-ai-coding-tools-what-they-are-and-how-the-top-options-compare) covers proprietary options such as Cursor and Claude Code. Sim fits when agents need to act across business systems with 270+ integrations, while OpenHands is the better pick when the agent's job is writing and shipping code. **Sources:** [OpenHands MIT license](https://github.com/OpenHands/OpenHands/blob/main/LICENSE), [OpenHands README](https://github.com/OpenHands/OpenHands). -## How do the open source AI agent platforms compare? +## How do open source AI agent platforms compare? + +Sim, n8n, and the other platforms in this comparison differ most in license, self-hosting model, visual-building experience, model flexibility, deployment controls, and intended user. License should be evaluated first because open source, source available, and free to self-host do not grant the same rights. -The platforms split cleanly on license and on how much of the production stack they include. All data was checked against each GitHub repository on October 20, 2026; stars show attention, not quality. +The platforms also split on how much of the production stack they include. All changing data was checked against each vendor's repository or first-party page as of November 2026; stars show attention, not quality. -| Tool | Type | Exact license | Visual builder | Self-hosting | Latest release (2026 unless noted) | GitHub stars (Oct 20, 2026) | +| Tool | Type | Exact license | Visual builder | Self-hosting | Latest release (2026 unless noted) | GitHub stars (as of November 2026) | |---|---|---|---|---|---|---:| -| **Sim** | AI workspace | Apache 2.0 core; Sim Enterprise License for `apps/sim/ee` | Yes | Docker Compose or Kubernetes | [v0.9.29, Oct 20](https://github.com/simstudioai/sim/releases) | [29,856](https://github.com/simstudioai/sim) | -| **LangGraph** | Code-first framework | MIT | No | Library in your infrastructure | [1.2.16, Oct 16](https://github.com/langchain-ai/langgraph/releases) | [43,597](https://github.com/langchain-ai/langgraph) | -| **CrewAI** | Code-first framework | MIT | In the hosted platform | Core in your infrastructure | [1.15.28, Oct 14](https://github.com/crewAIInc/crewAI/releases) | [59,924](https://github.com/crewAIInc/crewAI) | -| **Microsoft Agent Framework** | Code-first framework | MIT | No | Library in your infrastructure | [python-1.22.0, Oct 15](https://github.com/microsoft/agent-framework/releases) | [14,235](https://github.com/microsoft/agent-framework) | -| **AG2** | Code-first framework | Apache 2.0 | No | Library in your infrastructure | [v1.2.0, Oct 11](https://github.com/ag2ai/ag2/releases) | [5,006](https://github.com/ag2ai/ag2) | -| **AutoGen** (unmaintained) | Code-first framework | MIT code | AutoGen Studio, prototyping only | Library; no longer maintained | [0.7.5, Sep 30, 2025](https://github.com/microsoft/autogen/releases) | [61,501](https://github.com/microsoft/autogen) | -| **Dify** | Visual LLM app builder | Modified Apache 2.0, not OSI-approved as modified | Yes | Docker Compose; multi-tenant use needs permission | [1.17.1, Sep 10](https://github.com/langgenius/dify/releases) | [159,059](https://github.com/langgenius/dify) | -| **n8n** | Workflow automation with AI | Sustainable Use License, source available | Yes | Community, Business, or Enterprise editions | [2.44.4, Oct 19](https://github.com/n8n-io/n8n/releases) | [208,154](https://github.com/n8n-io/n8n) | -| **OpenHands** | Coding-agent platform | MIT | Coding-agent interface | Local, Docker, VMs, or own servers | [v1.29.0, Oct 20](https://github.com/OpenHands/OpenHands/releases) | [91,412](https://github.com/OpenHands/OpenHands) | -| **Flowise** (end of life) | Visual builder | Apache 2.0 outside commercially licensed enterprise code | Yes | Existing installs only; archived | [3.1.4, Jul 29](https://github.com/FlowiseAI/Flowise/releases) | [55,511](https://github.com/FlowiseAI/Flowise) | +| **Sim** | AI workspace | [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE); [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) for `apps/sim/ee` | Yes | Docker Compose or Kubernetes | [v0.9.56, Nov 13](https://github.com/simstudioai/sim/releases) | [30,132](https://github.com/simstudioai/sim) | +| **LangGraph** | Code-first framework | MIT | No | Library in your infrastructure | [1.2.21, Nov 12](https://github.com/langchain-ai/langgraph/releases) | [44,908](https://github.com/langchain-ai/langgraph) | +| **CrewAI** | Code-first framework | MIT | In the hosted platform | Core in your infrastructure | [1.15.41, Nov 12](https://github.com/crewAIInc/crewAI/releases) | [60,854](https://github.com/crewAIInc/crewAI) | +| **Microsoft Agent Framework** | Code-first framework | MIT | No | Library in your infrastructure | [python-1.26.0, Nov 13](https://github.com/microsoft/agent-framework/releases) | [14,660](https://github.com/microsoft/agent-framework) | +| **AG2** | Code-first framework | Apache 2.0 | No | Library in your infrastructure | [v1.3.1, Nov 10](https://github.com/ag2ai/ag2/releases) | [5,061](https://github.com/ag2ai/ag2) | +| **AutoGen** (maintenance mode) | Code-first framework | MIT code | AutoGen Studio, prototyping only | Library; maintenance mode | [0.7.5, Sep 30, 2025](https://github.com/microsoft/autogen/releases) | [61,805](https://github.com/microsoft/autogen) | +| **Dify** | Visual LLM app builder | Modified Apache 2.0, not OSI-approved as modified | Yes | Docker Compose; multi-tenant use needs permission | [1.19.0, Nov 4](https://github.com/langgenius/dify/releases) | [161,100](https://github.com/langgenius/dify) | +| **n8n** | Workflow automation with AI | Sustainable Use License, source available | Yes | Community, Business, or Enterprise editions | [1.123.95, Nov 12](https://github.com/n8n-io/n8n/releases) | [210,386](https://github.com/n8n-io/n8n) | +| **OpenHands** | Coding-agent platform | MIT | Coding-agent interface | Local, Docker, VMs, or own servers | [v1.40.0, Nov 12](https://github.com/OpenHands/OpenHands/releases) | [93,411](https://github.com/OpenHands/OpenHands) | +| **Flowise** (end of life) | Visual builder | Apache 2.0 outside commercially licensed enterprise code | Yes | Existing installs only; archived | [3.1.4, Jul 29](https://github.com/FlowiseAI/Flowise/releases) | [55,569](https://github.com/FlowiseAI/Flowise) | + +**Flowise key facts at a glance:** As of November 2026, Flowise's repository is archived, and existing installations remain self-hostable under its split open-source and commercial licensing. Pricing follows the same split. The frameworks are free libraries that push production tooling into paid services (LangSmith, CrewAI Enterprise). Sim and Dify offer free tiers with paid cloud plans. n8n's self-hosted Community edition is free, but its license has commercial-use limits worth reading before you build a product on it. -## How should you choose an open source AI agent platform? +## Choosing an open source AI agent platform + +Choose by license rights, self-hosting requirements, model support, integration coverage, deployment controls, observability, collaboration, and the technical skill required to operate the platform. Sim fits teams that prioritize an OSI-approved core, visual building, and self-hosted local-model support, while another retained platform may fit a narrower framework or coding preference. -Choose by team composition and use case first, then compare within that category. Comparing a Python framework against a visual workspace on one checklist is how teams end up six months into a tool that does not fit. +A practical evaluation should answer whether the license permits the intended production and commercial use, whether the platform can run in the required cloud or self-hosted environment, whether it supports the team's preferred hosted and local models, and whether its operational burden matches the team's engineering capacity. Comparing a Python framework against a visual workspace on one checklist is how teams end up six months into a tool that does not fit. - **Choose LangGraph** if engineers need fine-grained state management, explicit branching, and human approvals in code. - **Choose CrewAI** if work splits naturally into role-based tasks and Flows give you enough event-driven control. @@ -281,7 +309,7 @@ Choose by team composition and use case first, then compare within that category - **Choose Dify** if retrieval over documents is the primary job and its multi-tenant and branding conditions do not affect you. - **Choose n8n** if you already run automations there and want to add AI steps without rebuilding. - **Choose OpenHands** if the job is software delivery: coding agents working on repositories, issues, and CI. -- **Choose Sim** if a mixed team needs real-time collaboration, many model providers, human review, and deployment as an API, chat, or MCP tools in one workspace with an Apache 2.0 core. +- **Choose Sim** if a mixed team needs real-time collaboration, many model providers, human review, and deployment as an API, chat, or MCP tools in one workspace with an [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE), with `apps/sim/ee` under the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). **Self-hosting is a second decision.** It gives your team more control over infrastructure and data while making it responsible for availability, backups, security patches, secrets, model endpoints, observability, upgrades, and capacity. Ask three questions separately: can the software run on your infrastructure, does the license permit your intended production or commercial use, and can your team operate it reliably over time. @@ -292,9 +320,9 @@ Choose by team composition and use case first, then compare within that category These guides go deeper on the decisions above. - [Best self-hosted AI workflow automation platforms in 2026](/library/best-self-hosted-ai-workflow-automation-platforms-2026) for deployment choices. -- [Apache 2.0 vs fair-code](/library/apache-2-0-vs-fair-code) for why license choice changes what you can build on a self-hosted platform. +- [Apache 2.0 vs Fair-Code: Why Sim's License Beats n8n's for Self-Hosting](/library/apache-2-0-vs-fair-code) for why license choice changes what you can build on a self-hosted platform. - [AI agent orchestration frameworks explained](/library/ai-agent-orchestration-frameworks-explained), [LangGraph alternatives](/library/langgraph-alternatives), and [the best multi-agent frameworks](/library/best-multi-agent-frameworks-2026) for the code-first category. - [Best AI workflow builders](/library/best-ai-workflow-builders) and [best no-code AI agent builders](/library/best-no-code-ai-agent-builders-2026) for visual builders. -- [Sim vs n8n vs OpenAI AgentKit](/library/openai-vs-n8n-vs-sim) for a direct product comparison, and [the best AI agent platforms in 2026](/library/best-ai-agent-platforms-2026) for commercial options alongside these. +- [Sim vs n8n vs OpenAI AgentKit](/library/openai-vs-n8n-vs-sim) for a direct product comparison, and [Best AI Agent Platforms and Builders in 2026](/library/best-ai-agent-platforms-2026) for commercial options alongside these. If your team needs a visual builder, many model providers, and deployment from one self-hostable workspace, [try Sim](https://www.sim.ai) and see whether the workspace model matches how your team works. diff --git a/apps/sim/hooks/queries/scoped-credentials.ts b/apps/sim/hooks/queries/scoped-credentials.ts index 5f71035e48b..1f507a1af5d 100644 --- a/apps/sim/hooks/queries/scoped-credentials.ts +++ b/apps/sim/hooks/queries/scoped-credentials.ts @@ -123,8 +123,19 @@ export function useUpdateScopedCredential() { */ const workloadIdentityKeys = { all: ['workloadIdentitySetup'] as const, - setup: (workspaceId: string | undefined, provider: string, sessionKey: string) => - [...workloadIdentityKeys.all, workspaceId ?? 'none', provider, sessionKey] as const, + setup: ( + workspaceId: string | undefined, + provider: string, + sessionKey: string, + newSubject: boolean + ) => + [ + ...workloadIdentityKeys.all, + workspaceId ?? 'none', + provider, + sessionKey, + newSubject ? 'new' : 'held', + ] as const, } /** A setup's subject is minted once per modal session and never refreshed. */ @@ -138,23 +149,32 @@ interface UseWorkloadIdentitySetupProps { enabled: boolean /** The credential being reconnected, if any. */ credentialId?: string + /** Ask for a new subject instead of the one Sim is holding for this person. */ + newSubject?: boolean } /** - * The trust setup for a new keyless AWS role or Databricks service principal: - * the issuer to register and the signed subject it must trust. Each fetch - * mints a new subject, so the key carries the modal session and the result - * never goes stale or refetches while the person is copying it. + * The trust setup for a new keyless credential: the issuer to register and the + * signed subject it must trust. Sim holds an unsaved subject for its person, so + * reopening the dialog shows the same one; a `newSubject` fetch issues another. + * The key carries the modal session, and the result never goes stale or + * refetches while the person is copying it. */ export function useWorkloadIdentitySetup(input: UseWorkloadIdentitySetupProps) { return useQuery({ - queryKey: workloadIdentityKeys.setup(input.workspaceId, input.provider, input.sessionKey), + queryKey: workloadIdentityKeys.setup( + input.workspaceId, + input.provider, + input.sessionKey, + Boolean(input.newSubject) + ), queryFn: async ({ signal }) => requestJson(prepareWorkloadIdentityContract, { body: { workspaceId: input.workspaceId ?? '', provider: input.provider, ...(input.credentialId ? { credentialId: input.credentialId } : {}), + ...(input.newSubject ? { newSubject: true } : {}), }, signal, }), diff --git a/apps/sim/lib/api/contracts/credentials.ts b/apps/sim/lib/api/contracts/credentials.ts index eeb55e89558..06bfa9f1e0a 100644 --- a/apps/sim/lib/api/contracts/credentials.ts +++ b/apps/sim/lib/api/contracts/credentials.ts @@ -613,6 +613,12 @@ export const workloadIdentitySubjectGrantShape = { expiresAt: z .string() .describe('ISO 8601 timestamp after which `subjectProof` can no longer create a credential.'), + subjectIssuedAt: z.string().describe('ISO 8601 timestamp when Sim first issued `subject`.'), + subjectHeldUntil: z + .string() + .describe( + 'ISO 8601 timestamp until which Sim holds `subject` for you if no credential saves it. Renewing the proof restarts the hold; once a credential saves the subject, it is no longer held.' + ), } as const export const prepareWorkloadIdentityContract = defineRouteContract({ @@ -623,6 +629,8 @@ export const prepareWorkloadIdentityContract = defineRouteContract({ provider: z.enum(WORKLOAD_IDENTITY_PROVIDERS), /** The credential being reconnected, so the dialog opens on its current mode. */ credentialId: z.string().min(1).max(255).optional(), + /** Issue a new subject instead of the one Sim is already holding for this person. */ + newSubject: z.boolean().optional(), }), response: { mode: 'json', @@ -638,6 +646,11 @@ export const prepareWorkloadIdentityContract = defineRouteContract({ z.object({ available: z.literal(true), ...workloadIdentitySubjectGrantShape, + /** Absent when Sim does not hold the subject: a reconnect that keeps its own. */ + subjectIssuedAt: workloadIdentitySubjectGrantShape.subjectIssuedAt.optional(), + subjectHeldUntil: workloadIdentitySubjectGrantShape.subjectHeldUntil.optional(), + /** Whether `subject` was issued earlier, so a trust configuration may already allow it. */ + subjectReused: z.boolean().optional(), reconnectAuthType: reconnectAuthTypeSchema, reconnectDelegation: reconnectDelegationSchema, reconnectCloud: reconnectCloudSchema, diff --git a/apps/sim/lib/api/contracts/v2/credentials.ts b/apps/sim/lib/api/contracts/v2/credentials.ts index f6f4493764e..f8546f51e6a 100644 --- a/apps/sim/lib/api/contracts/v2/credentials.ts +++ b/apps/sim/lib/api/contracts/v2/credentials.ts @@ -442,6 +442,14 @@ const v2CreateCredentialWorkloadIdentitySubjectBodySchema = z .describe( 'Service-account provider the keyless credential will be created under; pass the same value to Create service-account credential. The response and List credential providers name the fields its keyless configuration needs.' ), + subject: z + .string() + .min(1) + .max(512) + .optional() + .describe( + 'A subject Sim issued to you earlier that no credential has saved yet. Renews its proof instead of issuing a new subject, so a trust configuration that already allows it keeps working. Refused with `WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING` once a credential saves it, after its hold ends, or for a subject issued to someone else.' + ), }) .strict() const v2CredentialWorkloadIdentitySubjectSchema = z @@ -450,6 +458,12 @@ const v2CredentialWorkloadIdentitySubjectSchema = z expiresAt: v2TimestampSchema.describe( workloadIdentitySubjectGrantShape.expiresAt.description ?? 'Proof expiry.' ), + subjectIssuedAt: v2TimestampSchema.describe( + workloadIdentitySubjectGrantShape.subjectIssuedAt.description ?? 'Subject issue time.' + ), + subjectHeldUntil: v2TimestampSchema.describe( + workloadIdentitySubjectGrantShape.subjectHeldUntil.description ?? 'Subject hold expiry.' + ), keylessConfig: z .object({ authType: z.string().describe("The provider's keyless authentication type."), diff --git a/apps/sim/lib/api/contracts/v2/openapi/resources.ts b/apps/sim/lib/api/contracts/v2/openapi/resources.ts index 9b9a66c2a73..e0d79e9531f 100644 --- a/apps/sim/lib/api/contracts/v2/openapi/resources.ts +++ b/apps/sim/lib/api/contracts/v2/openapi/resources.ts @@ -619,6 +619,8 @@ const WORKLOAD_IDENTITY_SUBJECT_EXAMPLE = { subject: WORKLOAD_IDENTITY_SUBJECT, subjectProof: WORKLOAD_IDENTITY_SUBJECT_PROOF, expiresAt: '2026-06-21T14:17:11.000Z', + subjectIssuedAt: '2026-06-20T14:17:11.000Z', + subjectHeldUntil: '2026-07-20T14:17:11.000Z', keylessConfig: { authType: 'oidc_role', subject: WORKLOAD_IDENTITY_SUBJECT, @@ -1742,7 +1744,7 @@ const declaredRoutes = [ applicationOperation: credentialOperations.createServiceAccount, operationId: 'createServiceAccountCredential', summary: 'Create service-account credential', - description: `Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in \`credentials\`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its \`keylessConfig\` as \`serviceAccountJson\`. A refused keyless credential names its cause in \`details.code\`: \`409\` for \`WORKLOAD_IDENTITY_SUBJECT_IN_USE\` or \`WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE\`, otherwise \`400\`, such as \`WORKLOAD_IDENTITY_PROOF_EXPIRED\` (request a new subject). Requires workspace write access. A matching source returns the existing credential with \`200\`; creation returns \`201\`. Stored secrets are omitted from the response. ${WORKSPACE_API_KEY_DENIED}`, + description: `Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in \`credentials\`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its \`keylessConfig\` as \`serviceAccountJson\`. A refused keyless credential names its cause in \`details.code\`: \`409\` for \`WORKLOAD_IDENTITY_SUBJECT_IN_USE\` or \`WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE\`, otherwise \`400\`, such as \`WORKLOAD_IDENTITY_PROOF_EXPIRED\` (renew the proof by passing the subject to Create workload identity subject). Requires workspace write access. A matching source returns the existing credential with \`200\`; creation returns \`201\`. Stored secrets are omitted from the response. ${WORKSPACE_API_KEY_DENIED}`, errors: RESOURCE_CONFLICT_ERRORS, success: { byStatus: { @@ -1840,7 +1842,7 @@ const declaredRoutes = [ applicationOperation: credentialOperations.prepareWorkloadIdentity, operationId: 'createCredentialWorkloadIdentitySubject', summary: 'Create workload identity subject', - description: `Start a keyless credential. Returns the issuer, audience, and a fresh subject to configure in the cloud's trust policy, plus \`keylessConfig\`: a prefilled \`serviceAccountJson\` whose \`subjectProof\` is valid for 24 hours and only for the requesting user. Add the \`requiredConfigFields\`, then call Create service-account credential with the same \`providerId\`; Sim verifies the trust while saving. The returned \`audience\` is the default; set \`audience\` in \`keylessConfig\` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns \`400\` with \`details.code\` \`WORKLOAD_IDENTITY_NOT_CONFIGURED\`. ${WORKSPACE_API_KEY_DENIED}`, + description: `Start a keyless credential. Returns the issuer, audience, and a new subject to configure in the cloud's trust policy (or, when \`subject\` names one Sim is holding for you, that same subject with a renewed proof), plus \`keylessConfig\`: a prefilled \`serviceAccountJson\` whose \`subjectProof\` is valid for 24 hours and only for the requesting user. Sim holds the subject for you until \`subjectHeldUntil\` (30 days, restarted by each renewal) unless a credential saves it first; pass it back as \`subject\` to renew the proof without changing the subject, so a trust policy that takes days to apply still matches. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. Add the \`requiredConfigFields\`, then call Create service-account credential with the same \`providerId\`; Sim verifies the trust while saving. The returned \`audience\` is the default; set \`audience\` in \`keylessConfig\` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns \`400\` with \`details.code\` \`WORKLOAD_IDENTITY_NOT_CONFIGURED\`. ${WORKSPACE_API_KEY_DENIED}`, errors: RESOURCE_ERRORS, success: { description: 'A subject and proof for one new keyless credential.' }, }), @@ -1850,8 +1852,15 @@ const declaredRoutes = [ v2CreateCredentialWorkloadIdentitySubjectContract.body, 'CreateCredentialWorkloadIdentitySubjectRequest', 'Create workload identity subject request', - 'Workspace that will own the keyless credential and the service-account provider it will be created under.', - [{ workspaceId: WORKSPACE_ID, providerId: 'aws-service-account' }] + 'Workspace that will own the keyless credential, the service-account provider it will be created under, and optionally a held subject whose proof to renew.', + [ + { workspaceId: WORKSPACE_ID, providerId: 'aws-service-account' }, + { + workspaceId: WORKSPACE_ID, + providerId: 'aws-service-account', + subject: WORKLOAD_IDENTITY_SUBJECT, + }, + ] ), response: documentedSchema( v2CreateCredentialWorkloadIdentitySubjectContract.response.schema, diff --git a/apps/sim/lib/api/mcp/generated/v2-operations.ts b/apps/sim/lib/api/mcp/generated/v2-operations.ts index bf693ccfc18..1e766adb85d 100644 --- a/apps/sim/lib/api/mcp/generated/v2-operations.ts +++ b/apps/sim/lib/api/mcp/generated/v2-operations.ts @@ -665,7 +665,7 @@ export const V2_MCP_OPERATIONS = { contract: v2CreateCredentialWorkloadIdentitySubjectContract, summary: 'Create workload identity subject', description: - "Start a keyless credential. Returns the issuer, audience, and a fresh subject to configure in the cloud's trust policy, plus `keylessConfig`: a prefilled `serviceAccountJson` whose `subjectProof` is valid for 24 hours and only for the requesting user. Add the `requiredConfigFields`, then call Create service-account credential with the same `providerId`; Sim verifies the trust while saving. The returned `audience` is the default; set `audience` in `keylessConfig` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns `400` with `details.code` `WORKLOAD_IDENTITY_NOT_CONFIGURED`. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", + "Start a keyless credential. Returns the issuer, audience, and a new subject to configure in the cloud's trust policy (or, when `subject` names one Sim is holding for you, that same subject with a renewed proof), plus `keylessConfig`: a prefilled `serviceAccountJson` whose `subjectProof` is valid for 24 hours and only for the requesting user. Sim holds the subject for you until `subjectHeldUntil` (30 days, restarted by each renewal) unless a credential saves it first; pass it back as `subject` to renew the proof without changing the subject, so a trust policy that takes days to apply still matches. Sim holds up to 20 unsaved subjects per person and provider in a workspace; issuing more drops the one shown least recently. Add the `requiredConfigFields`, then call Create service-account credential with the same `providerId`; Sim verifies the trust while saving. The returned `audience` is the default; set `audience` in `keylessConfig` to have Sim sign tokens for your own value instead, and trust that value in the cloud. Google Cloud has no fixed default: it is the credential's own pool provider. Without keyless support the deployment returns `400` with `details.code` `WORKLOAD_IDENTITY_NOT_CONFIGURED`. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", workspaceKeyUnsupported: true, handler: () => import('@/app/api/v2/credentials/workload-identity/subjects/route').then( @@ -839,7 +839,7 @@ export const V2_MCP_OPERATIONS = { contract: v2CreateServiceAccountCredentialContract, summary: 'Create service-account credential', description: - 'Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in `credentials`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its `keylessConfig` as `serviceAccountJson`. A refused keyless credential names its cause in `details.code`: `409` for `WORKLOAD_IDENTITY_SUBJECT_IN_USE` or `WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE`, otherwise `400`, such as `WORKLOAD_IDENTITY_PROOF_EXPIRED` (request a new subject). Requires workspace write access. A matching source returns the existing credential with `200`; creation returns `201`. Stored secrets are omitted from the response. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.', + 'Verify and store a service-account credential. Use the fields from List credential providers, encoded as a JSON object string in `credentials`. For a keyless AWS, Databricks, Snowflake, Microsoft app, or Google Cloud credential, call Create workload identity subject first, configure the cloud to trust it, then send its `keylessConfig` as `serviceAccountJson`. A refused keyless credential names its cause in `details.code`: `409` for `WORKLOAD_IDENTITY_SUBJECT_IN_USE` or `WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE`, otherwise `400`, such as `WORKLOAD_IDENTITY_PROOF_EXPIRED` (renew the proof by passing the subject to Create workload identity subject). Requires workspace write access. A matching source returns the existing credential with `200`; creation returns `201`. Stored secrets are omitted from the response. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.', workspaceKeyUnsupported: true, handler: () => import('@/app/api/v2/credentials/route').then((route) => route.POST), }, diff --git a/apps/sim/lib/billing/aws-marketplace/container-license.test.ts b/apps/sim/lib/billing/aws-marketplace/container-license.test.ts index d9c817ba494..d78fc17dae0 100644 --- a/apps/sim/lib/billing/aws-marketplace/container-license.test.ts +++ b/apps/sim/lib/billing/aws-marketplace/container-license.test.ts @@ -50,7 +50,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { checkoutRequests.push(command.input) const answer = answers.length > 1 ? answers.shift() : answers[0] if (answer !== 'entitled') throw awsError(answer ?? 'ServerInternalException') - return { EntitlementsAllowed: [{ Name: 'enterprise_license', Unit: 'None' }] } + return { EntitlementsAllowed: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }] } }) }) @@ -68,7 +68,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { expect(mockSend).not.toHaveBeenCalled() }) - it('turns Enterprise on with an uncounted checkout of the entitlement', async () => { + it('turns Enterprise on with an uncounted checkout of the Marketplace usage entitlement', async () => { answers = ['entitled'] await applyAwsMarketplaceContainerLicense() expect(enterpriseEnabled()).toBe(true) @@ -77,7 +77,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { ProductSKU: 'product-placeholder', CheckoutType: 'PROVISIONAL', KeyFingerprint: 'aws:294406891311:AWS/Marketplace:issuer-fingerprint', - Entitlements: [{ Name: 'enterprise_license', Unit: 'None' }], + Entitlements: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }], }), ]) }) diff --git a/apps/sim/lib/billing/aws-marketplace/container-license.ts b/apps/sim/lib/billing/aws-marketplace/container-license.ts index fd4d53d6ab9..b190c924b9b 100644 --- a/apps/sim/lib/billing/aws-marketplace/container-license.ts +++ b/apps/sim/lib/billing/aws-marketplace/container-license.ts @@ -8,14 +8,13 @@ import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server' /** * Enterprise entitlement for Sim installed from the AWS Marketplace container listing. * - * The listing sells an annual contract whose one dimension is `enterprise_license`. AWS Marketplace - * grants the buyer a license in AWS License Manager, and the running product checks it the way AWS - * documents for a contract that unlocks a tier of features rather than metering a quantity: a - * provisional `CheckoutLicense` of the entitlement with unit `None`. Such a checkout is not counted - * against the contract, so every replica can check at once, and it needs only the permission AWS's - * managed `AWSLicenseManagerConsumptionPolicy` grants. While the license is active, the Enterprise - * suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed - * renewal or an unreachable License Manager never locks a buyer out of their deployment. + * The listing sells an annual contract. The buyer's AWS License Manager license carries the + * contract's counted dimensions plus an uncounted `AWS::Marketplace::Usage` entitlement held while + * the agreement is active. Sim checks the uncounted one with a provisional `CheckoutLicense`, so + * every replica can check at once (a counted unit would go to one replica only), using only the + * permission in AWS's managed `AWSLicenseManagerConsumptionPolicy`. While the license is active, the + * Enterprise suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed renewal or + * an unreachable License Manager never locks a buyer out of their deployment. * * Opt-in: nothing here runs unless {@link PRODUCT_SKU_ENV} is set, which only the Marketplace * Helm profile does. Enterprise flags are read once at startup by both the server and the @@ -38,8 +37,8 @@ const LICENSE_REGION_ENV = 'AWS_MARKETPLACE_LICENSE_REGION' /** The issuer every AWS Marketplace license is signed by (a fixed value, not a placeholder). */ const MARKETPLACE_KEY_FINGERPRINT = 'aws:294406891311:AWS/Marketplace:issuer-fingerprint' -/** The listing's contract dimension API name. */ -const ENTERPRISE_ENTITLEMENT = 'enterprise_license' +/** The uncounted entitlement every AWS Marketplace license holds while its agreement is active. */ +const MARKETPLACE_USAGE_ENTITLEMENT = 'AWS::Marketplace::Usage' /** AWS recommends re-checking every 15 minutes to pick up renewals, upgrades, and cancellations. */ const RECHECK_INTERVAL_MS = 15 * 60 * 1000 @@ -68,7 +67,10 @@ const NO_ENTITLEMENT_ERRORS = new Set([ 'ResourceNotFoundException', ]) -/** The ServiceAccount's IAM role does not allow the check: a setup problem, never a verdict. */ +/** + * The check was refused: the ServiceAccount's IAM role lacks the permission, or License Manager's + * service-linked role is missing from the account. A setup problem, never a verdict. + */ const PERMISSION_ERRORS = new Set(['AccessDeniedException', 'AuthorizationException']) /** `unknown` is a failed check (network, permissions, throttling), never a verdict on the license. */ @@ -93,15 +95,15 @@ function classifyFailure(error: unknown): LicenseState { return 'not-entitled' } if (name && PERMISSION_ERRORS.has(name)) { - logger.error( - 'The IAM role cannot check the AWS Marketplace license; allow license-manager:CheckoutLicense', - { error: name } - ) + logger.error('AWS License Manager refused the license check', { + errorName: name, + error: getErrorMessage(error, 'Unknown error'), + }) return 'unknown' } logger.warn('Could not check the AWS Marketplace Enterprise license', { - error: name, - message: getErrorMessage(error, 'Unknown error'), + errorName: name, + error: getErrorMessage(error, 'Unknown error'), }) return 'unknown' } @@ -116,12 +118,12 @@ async function checkLicense( ProductSKU: productSku, CheckoutType: 'PROVISIONAL', KeyFingerprint: MARKETPLACE_KEY_FINGERPRINT, - Entitlements: [{ Name: ENTERPRISE_ENTITLEMENT, Unit: 'None' }], + Entitlements: [{ Name: MARKETPLACE_USAGE_ENTITLEMENT, Unit: 'None' }], ClientToken: generateId(), }) ) const allowed = (checkout.EntitlementsAllowed ?? []).some( - (entitlement) => entitlement.Name === ENTERPRISE_ENTITLEMENT + (entitlement) => entitlement.Name === MARKETPLACE_USAGE_ENTITLEMENT ) return allowed ? 'entitled' : 'not-entitled' } catch (error) { diff --git a/apps/sim/lib/block-metadata/names.generated.ts b/apps/sim/lib/block-metadata/names.generated.ts index c064312cb2b..874a0b4f81d 100644 --- a/apps/sim/lib/block-metadata/names.generated.ts +++ b/apps/sim/lib/block-metadata/names.generated.ts @@ -186,6 +186,7 @@ export const BLOCK_NAMES: Readonly> = { mintlify: 'Mintlify', mistral_parse_v3: 'Mistral Parser', modal: 'Modal', + monaco: 'Monaco', monday: 'Monday', mongodb: 'MongoDB', mothership: 'Sim Chat', diff --git a/apps/sim/lib/credentials/__integration__/workload-identity-pending-subject.integration.ts b/apps/sim/lib/credentials/__integration__/workload-identity-pending-subject.integration.ts new file mode 100644 index 00000000000..a110b91cb23 --- /dev/null +++ b/apps/sim/lib/credentials/__integration__/workload-identity-pending-subject.integration.ts @@ -0,0 +1,352 @@ +/** + * Sim holds an unsaved keyless subject for the person it was issued to, so a + * trust policy applied days after the connect dialog closed still matches: the + * dialog reopens on the same subject, the proof can be renewed, and a subject + * a credential has saved is never handed out again. + */ + +import { db } from '@sim/db' +import { + credential, + permissions, + user, + workloadIdentityPendingSubject, + workspace, +} from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' +import { createSessionPrincipal } from '@sim/testing/factories/principal.factory' +import { jsonResponse } from '@sim/testing/helpers/http' +import { posthogServerMock } from '@sim/testing/mocks/posthog-server.mock' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray } from 'drizzle-orm' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' + +vi.mock('@/lib/posthog/server', () => posthogServerMock) +vi.mock('@/lib/credentials/workload-identity/issuer', async (importOriginal) => ({ + ...(await importOriginal()), + isWorkloadIdentityConfigured: async () => true, + mintWorkloadIdentityToken: async ({ subject }: { subject: string }) => `token-for:${subject}`, +})) +/** The identity check goes through the egress guard, not global fetch; serve its fixture there. */ +vi.mock('@/lib/core/security/input-validation.server', async (importOriginal) => { + const actual = + await importOriginal() + return { + ...actual, + secureFetchWithValidation: async ( + ...args: Parameters + ) => { + if (args[0] !== 'https://dbc-5678abcd-1234.cloud.databricks.com/api/2.0/preview/scim/v2/Me') + return actual.secureFetchWithValidation(...args) + const body = JSON.stringify({ userName: 'service-principal@example.com' }) + return { + ok: true, + status: 200, + statusText: 'OK', + headers: new actual.SecureFetchHeaders({ 'content-type': 'application/json' }), + body: null, + text: async () => body, + json: async () => JSON.parse(body), + arrayBuffer: async () => new TextEncoder().encode(body).buffer, + } + }, + } +}) + +import { prepareWorkloadIdentity } from '@/lib/credentials/application/prepare-workload-identity' +import { updateCredentialRecord } from '@/lib/credentials/orchestration' +import { createCredentialRecord } from '@/lib/credentials/orchestration/credential-create' +import { + PENDING_SUBJECT_HOLD_MS, + resolvePendingWorkloadIdentitySubject, +} from '@/lib/credentials/workload-identity/pending-subjects' +import { signWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/subject' +import { DATABRICKS_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' + +const HOST = 'dbc-5678abcd-1234.cloud.databricks.com' +const CLIENT_ID = '00000000-0000-0000-0000-000000000000' +const DAY_MS = 24 * 60 * 60 * 1000 +const userId = generateId() +const otherUserId = generateId() +const workspaceId = generateId() +const owner = { workspaceId } +const issuer = { provider: 'databricks' as const, workspaceId, userId } +const originalFetch = globalThis.fetch + +/** Subjects the fixture service principal's federation policy pins. */ +const pinnedSubjects = new Set() + +function keyless(subject: string, actor = userId) { + const { subjectProof } = signWorkloadIdentitySubject('databricks', owner, actor, subject) + return JSON.stringify({ + authType: 'oidc_federation', + host: HOST, + clientId: CLIENT_ID, + subject, + subjectProof, + }) +} + +function create(displayName: string, serviceAccountJson: string) { + return createCredentialRecord( + { + workspaceId, + type: 'service_account', + userId, + providerId: DATABRICKS_SERVICE_ACCOUNT_PROVIDER_ID, + displayName, + serviceAccountJson, + }, + { authorizeWorkspace: true } + ) +} + +async function heldSubjects(): Promise { + const rows = await db + .select({ subject: workloadIdentityPendingSubject.subject }) + .from(workloadIdentityPendingSubject) + .where(eq(workloadIdentityPendingSubject.workspaceId, workspaceId)) + return rows.map((row) => row.subject) +} + +describe('holding an unsaved keyless subject', () => { + beforeAll(async () => { + const now = new Date() + await db.insert(user).values( + [userId, otherUserId].map((id) => ({ + id, + name: 'Pending subject fixture', + email: `${id}@pending-subject.test`, + emailVerified: true, + createdAt: now, + updatedAt: now, + })) + ) + await insertWorkspaceFixture(db, { + id: workspaceId, + name: 'Pending subject fixture', + ownerId: userId, + billedAccountUserId: userId, + }) + await db.insert(permissions).values( + [userId, otherUserId].map((id) => ({ + id: generateId(), + userId: id, + entityType: 'workspace' as const, + entityId: workspaceId, + permissionType: 'admin' as const, + })) + ) + + vi.stubGlobal('fetch', async (input: string | URL | Request, init?: RequestInit) => { + const target = typeof input === 'string' || input instanceof URL ? String(input) : input.url + if (target === `https://${HOST}/oidc/v1/token`) { + const subjectToken = new URLSearchParams(String(init?.body)).get('subject_token') ?? '' + const subject = subjectToken.replace(/^token-for:/, '') + return pinnedSubjects.has(subject) + ? jsonResponse({ access_token: `access-for:${subject}` }) + : jsonResponse({ error: 'invalid_client' }, 401) + } + return originalFetch(input, init) + }) + }, 60_000) + + afterAll(async () => { + vi.unstubAllGlobals() + await db.delete(credential).where(eq(credential.workspaceId, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) + await db.delete(user).where(inArray(user.id, [userId, otherUserId])) + }) + + it('reopens on the same subject until a new one is asked for', async () => { + const first = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'latest' }) + const reopened = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'latest' }) + const fresh = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }) + const afterFresh = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'latest' }) + + expect(reopened).toMatchObject({ subject: first.subject, reused: true }) + expect(reopened.issuedAt.getTime()).toBe(first.issuedAt.getTime()) + expect(fresh.subject).not.toBe(first.subject) + expect(fresh.reused).toBe(false) + expect(afterFresh.subject).toBe(fresh.subject) + }) + + it('saves a credential days later on a renewed proof for the held subject', async () => { + const tenDaysAgo = new Date(Date.now() - 10 * DAY_MS) + const held = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }, tenDaysAgo) + pinnedSubjects.add(held.subject) + + const renewed = await prepareWorkloadIdentity.execute({ + principal: createSessionPrincipal({ userId }), + input: { + workspaceId, + provider: 'databricks', + subjectRequest: { kind: 'renew', subject: held.subject }, + }, + }) + if (!renewed.available) throw new Error('Keyless signing should be available in this suite') + const saved = await create( + 'Renewed lakehouse', + JSON.stringify({ + authType: 'oidc_federation', + host: HOST, + clientId: CLIENT_ID, + subject: renewed.subject, + subjectProof: renewed.subjectProof, + }) + ) + + expect(renewed.subject).toBe(held.subject) + expect(renewed.subjectIssuedAt).toBe(tenDaysAgo.toISOString()) + expect(Date.parse(renewed.subjectHeldUntil ?? '')).toBeGreaterThan( + Date.now() + PENDING_SUBJECT_HOLD_MS - DAY_MS + ) + expect(saved.success).toBe(true) + }) + + it('never offers a subject again once a credential saved it, even after that credential is gone', async () => { + const held = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }) + pinnedSubjects.add(held.subject) + const saved = await create('Saved lakehouse', keyless(held.subject)) + const savedId = saved.credential?.id ?? '' + expect(savedId).not.toBe('') + + await db.delete(credential).where(eq(credential.id, savedId)) + const next = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'latest' }) + + expect(await heldSubjects()).not.toContain(held.subject) + expect(next.subject).not.toBe(held.subject) + await expect( + resolvePendingWorkloadIdentitySubject(issuer, { kind: 'renew', subject: held.subject }) + ).rejects.toMatchObject({ detailCode: 'WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING' }) + }) + + it('drops a held subject that a credential saved without releasing it', async () => { + const held = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }) + pinnedSubjects.add(held.subject) + const saved = await create('Unreleased lakehouse', keyless(held.subject)) + expect(saved.success).toBe(true) + // A save on a release without pending subjects leaves the row behind. + await db.insert(workloadIdentityPendingSubject).values({ + id: generateId(), + workspaceId, + userId, + provider: 'databricks', + subject: held.subject, + createdAt: new Date(), + expiresAt: new Date(Date.now() + DAY_MS), + }) + + const next = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'latest' }) + + expect(next.subject).not.toBe(held.subject) + expect(await heldSubjects()).not.toContain(held.subject) + }) + + it('releases a held subject a reconnect moves the credential onto', async () => { + const first = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }) + pinnedSubjects.add(first.subject) + const saved = await create('Reconnected lakehouse', keyless(first.subject)) + const savedId = saved.credential?.id ?? '' + expect(savedId).not.toBe('') + const moved = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }) + pinnedSubjects.add(moved.subject) + + const [row] = await db.select().from(credential).where(eq(credential.id, savedId)) + const reconnected = await updateCredentialRecord({ + credentialId: savedId, + credential: row, + actorUserId: userId, + serviceAccountJson: keyless(moved.subject), + }) + + expect(reconnected.success).toBe(true) + expect(await heldSubjects()).not.toContain(moved.subject) + }) + + it('refuses to renew a subject held for someone else', async () => { + const theirs = await resolvePendingWorkloadIdentitySubject( + { ...issuer, userId: otherUserId }, + { kind: 'new' } + ) + + await expect( + resolvePendingWorkloadIdentitySubject(issuer, { kind: 'renew', subject: theirs.subject }) + ).rejects.toMatchObject({ detailCode: 'WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING' }) + }) + + it('stops holding a subject that was not shown again within the hold', async () => { + const longAgo = new Date(Date.now() - PENDING_SUBJECT_HOLD_MS - DAY_MS) + const stale = await resolvePendingWorkloadIdentitySubject(issuer, { kind: 'new' }, longAgo) + + await expect( + resolvePendingWorkloadIdentitySubject(issuer, { kind: 'renew', subject: stale.subject }) + ).rejects.toMatchObject({ detailCode: 'WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING' }) + expect(await heldSubjects()).not.toContain(stale.subject) + }) + + it('keeps holding a subject while an unreadable sibling credential blocks the check', async () => { + const google = { ...issuer, provider: 'google' as const } + const held = await resolvePendingWorkloadIdentitySubject(google, { kind: 'new' }) + const unreadableId = generateId() + await db.insert(credential).values({ + id: unreadableId, + workspaceId, + type: 'service_account', + displayName: 'Unreadable keyless credential', + providerId: DATABRICKS_SERVICE_ACCOUNT_PROVIDER_ID, + encryptedServiceAccountKey: 'not-a-ciphertext', + createdBy: userId, + }) + + const reopened = await resolvePendingWorkloadIdentitySubject(google, { kind: 'latest' }) + await db.delete(credential).where(eq(credential.id, unreadableId)) + + expect(reopened).toMatchObject({ subject: held.subject, reused: true }) + expect(await heldSubjects()).toContain(held.subject) + }) + + it('keeps a subject shown recently when the cap drops older ones', async () => { + const capped = { ...issuer, provider: 'microsoft' as const } + const tenDaysAgo = new Date(Date.now() - 10 * DAY_MS) + const underReview = await resolvePendingWorkloadIdentitySubject( + capped, + { kind: 'new' }, + tenDaysAgo + ) + for (let index = 0; index < 19; index++) { + await resolvePendingWorkloadIdentitySubject( + capped, + { kind: 'new' }, + new Date(tenDaysAgo.getTime() + (index + 1) * 60_000) + ) + } + await resolvePendingWorkloadIdentitySubject(capped, { + kind: 'renew', + subject: underReview.subject, + }) + + await resolvePendingWorkloadIdentitySubject(capped, { kind: 'new' }) + + expect(await heldSubjects()).toContain(underReview.subject) + }) + + it('keeps one person from piling up held subjects', async () => { + const capped = { ...issuer, provider: 'snowflake' as const } + for (let index = 0; index < 25; index++) { + await resolvePendingWorkloadIdentitySubject(capped, { kind: 'new' }) + } + + const rows = await db + .select({ id: workloadIdentityPendingSubject.id }) + .from(workloadIdentityPendingSubject) + .where( + and( + eq(workloadIdentityPendingSubject.workspaceId, workspaceId), + eq(workloadIdentityPendingSubject.provider, 'snowflake') + ) + ) + + expect(rows.length).toBe(20) + }) +}) diff --git a/apps/sim/lib/credentials/__integration__/workload-identity-subject-reuse.integration.ts b/apps/sim/lib/credentials/__integration__/workload-identity-subject-reuse.integration.ts index ed303319c5e..b9ac6c64f59 100644 --- a/apps/sim/lib/credentials/__integration__/workload-identity-subject-reuse.integration.ts +++ b/apps/sim/lib/credentials/__integration__/workload-identity-subject-reuse.integration.ts @@ -19,6 +19,31 @@ vi.mock('@/lib/credentials/workload-identity/issuer', async (importOriginal) => isWorkloadIdentityConfigured: async () => true, mintWorkloadIdentityToken: async ({ subject }: { subject: string }) => `token-for:${subject}`, })) +/** The identity check goes through the egress guard, not global fetch; serve its fixture there. */ +vi.mock('@/lib/core/security/input-validation.server', async (importOriginal) => { + const actual = + await importOriginal() + return { + ...actual, + secureFetchWithValidation: async ( + ...args: Parameters + ) => { + if (args[0] !== 'https://dbc-1234abcd-5678.cloud.databricks.com/api/2.0/preview/scim/v2/Me') + return actual.secureFetchWithValidation(...args) + const body = JSON.stringify({ userName: 'service-principal@example.com' }) + return { + ok: true, + status: 200, + statusText: 'OK', + headers: new actual.SecureFetchHeaders({ 'content-type': 'application/json' }), + body: null, + text: async () => body, + json: async () => JSON.parse(body), + arrayBuffer: async () => new TextEncoder().encode(body).buffer, + } + }, + } +}) import { encryptSecret } from '@/lib/core/security/encryption' import { updateCredentialRecord } from '@/lib/credentials/orchestration' @@ -114,9 +139,6 @@ describe('reusing a keyless trust setup', () => { ? jsonResponse({ access_token: `access-for:${subject}` }) : jsonResponse({ error: 'invalid_client' }, 401) } - if (target === `https://${HOST}/api/2.0/preview/scim/v2/Me`) { - return jsonResponse({ userName: 'service-principal@example.com' }) - } return originalFetch(input, init) }) }, 60_000) diff --git a/apps/sim/lib/credentials/application/prepare-workload-identity.test.ts b/apps/sim/lib/credentials/application/prepare-workload-identity.test.ts index eedb4b1dabc..ec9344be03a 100644 --- a/apps/sim/lib/credentials/application/prepare-workload-identity.test.ts +++ b/apps/sim/lib/credentials/application/prepare-workload-identity.test.ts @@ -19,6 +19,28 @@ vi.mock('@/lib/workspaces/application/context', () => workspaceContextMock) vi.mock('@sim/platform-authz/workspace', () => workspaceAuthzMock) vi.mock('@/lib/core/security/encryption', () => encryptionMock) vi.mock('@/lib/credentials/access', () => credentialsAccessMock) +vi.mock('@/lib/credentials/workload-identity/pending-subjects', async () => { + const { newWorkloadIdentitySubject } = await vi.importActual< + typeof import('@/lib/credentials/workload-identity/subject') + >('@/lib/credentials/workload-identity/subject') + const actual = await vi.importActual< + typeof import('@/lib/credentials/workload-identity/pending-subjects') + >('@/lib/credentials/workload-identity/pending-subjects') + return { + ...actual, + resolvePendingWorkloadIdentitySubject: vi.fn( + async ({ workspaceId }: { workspaceId: string }) => { + const issuedAt = new Date() + return { + subject: newWorkloadIdentitySubject({ workspaceId }), + issuedAt, + heldUntil: new Date(issuedAt.getTime() + actual.PENDING_SUBJECT_HOLD_MS), + reused: false, + } + } + ), + } +}) import { env } from '@/lib/core/config/env' import { prepareWorkloadIdentity } from '@/lib/credentials/application/prepare-workload-identity' @@ -58,7 +80,12 @@ function prepare( ) { return prepareWorkloadIdentity.execute({ principal, - input: { workspaceId: 'workspace-1', provider, credentialId: 'cred-1' }, + input: { + workspaceId: 'workspace-1', + provider, + credentialId: 'cred-1', + subjectRequest: { kind: 'latest' }, + }, }) } @@ -334,6 +361,25 @@ describe('preparing a reconnect', () => { expect(await prepare()).not.toHaveProperty('reconnectCloud') }) + it('reports no hold for a reconnect subject Sim does not hold', async () => { + reconnecting(keylessSecret) + + const result = await prepareWorkloadIdentity.execute({ + principal, + input: { + workspaceId: 'workspace-1', + provider: 'snowflake', + credentialId: 'cred-1', + subjectRequest: { kind: 'unheld' }, + }, + }) + + expect(result).toMatchObject({ available: true, subject: expect.any(String) }) + expect(result).not.toHaveProperty('subjectHeldUntil') + expect(result).not.toHaveProperty('subjectIssuedAt') + expect(result).not.toHaveProperty('subjectReused') + }) + it('gives Google no fixed audience', async () => { reconnecting(STORED_MODES[11].secret, { providerId: 'google-service-account' }) expect(await prepare('google')).not.toHaveProperty('audience') diff --git a/apps/sim/lib/credentials/application/prepare-workload-identity.ts b/apps/sim/lib/credentials/application/prepare-workload-identity.ts index 2cee3688fde..22d23626f81 100644 --- a/apps/sim/lib/credentials/application/prepare-workload-identity.ts +++ b/apps/sim/lib/credentials/application/prepare-workload-identity.ts @@ -24,7 +24,14 @@ import { getWorkloadIdentityIssuer, isWorkloadIdentityConfigured, } from '@/lib/credentials/workload-identity/issuer' -import { issueWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/subject' +import { + type PendingSubjectRequest, + resolvePendingWorkloadIdentitySubject, +} from '@/lib/credentials/workload-identity/pending-subjects' +import { + issueWorkloadIdentitySubject, + signWorkloadIdentitySubject, +} from '@/lib/credentials/workload-identity/subject' import { loadActiveWorkspaceApplicationContext } from '@/lib/workspaces/application/context' export interface PrepareWorkloadIdentityInput { @@ -32,6 +39,11 @@ export interface PrepareWorkloadIdentityInput { provider: WorkloadIdentityProvider /** The credential being reconnected, so the dialog can open on its current mode. */ credentialId?: string + /** + * Which subject to hand out; see {@link PendingSubjectRequest}. `unheld` issues one + * Sim does not hold, for a reconnect that keeps its current subject and never shows it. + */ + subjectRequest: PendingSubjectRequest | { kind: 'unheld' } } /** @@ -59,6 +71,12 @@ export type PrepareWorkloadIdentityResult = subject: string subjectProof: string expiresAt: string + /** When Sim first issued `subject`; absent when Sim does not hold it (an `unheld` request). */ + subjectIssuedAt?: string + /** Until when Sim keeps holding `subject` for this person if no credential saves it. */ + subjectHeldUntil?: string + /** Whether `subject` was issued earlier, so a trust configuration may already allow it. */ + subjectReused?: boolean } & ReconnectFields) /** What a provider's stored-secret reader returns: the non-secret fields a reconnect keeps. */ @@ -129,11 +147,31 @@ async function storedReconnectFields( return STORED_RECONNECT[provider](decrypted) } +/** The signed subject to hand out, and Sim's hold on it; no hold for an `unheld` request. */ +async function subjectGrant( + input: PrepareWorkloadIdentityInput, + owner: { workspaceId: string }, + userId: string +) { + if (input.subjectRequest.kind === 'unheld') { + return { grant: issueWorkloadIdentitySubject(input.provider, owner, userId), pending: null } + } + const pending = await resolvePendingWorkloadIdentitySubject( + { provider: input.provider, workspaceId: input.workspaceId, userId }, + input.subjectRequest + ) + return { + grant: signWorkloadIdentitySubject(input.provider, owner, userId, pending.subject), + pending, + } +} + /** * Starts a keyless cloud connection (an AWS role, a Databricks service * principal, a Snowflake service user, a Microsoft app, or a Google service account): the subject the customer's trust * configuration will pin, signed for this person and workspace so only they can attach it to the credential - * they are about to create. Reports `available: false` when this deployment + * they are about to create. Sim holds the subject for them until a credential saves it, so a trust + * configuration applied days later still matches. Reports `available: false` when this deployment * cannot sign tokens, so the connect dialog offers only key-based modes. On a * reconnect it also reports the credential's stored mode, signing or not. */ @@ -157,17 +195,21 @@ export const prepareWorkloadIdentity = defineAuthorizedWorkspaceUseCase({ // A deployment without a signing key offers only key- and secret-based credentials. if (!(await isWorkloadIdentityConfigured())) return { available: false, ...reconnect } const userId = requirePrincipalSubjectUserId(principal) - const grant = issueWorkloadIdentitySubject( - input.provider, - { workspaceId: input.workspaceId }, - userId - ) + const owner = { workspaceId: input.workspaceId } + const { grant, pending } = await subjectGrant(input, owner, userId) const audience = fixedWorkloadIdentityAudience(input.provider) return { available: true, issuer: getWorkloadIdentityIssuer(), ...(audience ? { audience } : {}), ...grant, + ...(pending + ? { + subjectIssuedAt: pending.issuedAt.toISOString(), + subjectHeldUntil: pending.heldUntil.toISOString(), + subjectReused: pending.reused, + } + : {}), ...reconnect, } }, diff --git a/apps/sim/lib/credentials/databricks-service-account.server.test.ts b/apps/sim/lib/credentials/databricks-service-account.server.test.ts index 50a8fb721ab..5659f3cfb28 100644 --- a/apps/sim/lib/credentials/databricks-service-account.server.test.ts +++ b/apps/sim/lib/credentials/databricks-service-account.server.test.ts @@ -2,7 +2,12 @@ * @vitest-environment node */ -import { queueTableRows, resetDbChainMock } from '@sim/testing' +import { + inputValidationMock, + inputValidationMockFns, + queueTableRows, + resetDbChainMock, +} from '@sim/testing' import { jsonResponse } from '@sim/testing/helpers/http' import { encryptionMock, encryptionMockFns } from '@sim/testing/mocks/encryption.mock' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -12,6 +17,7 @@ const hoisted = vi.hoisted(() => ({ })) vi.mock('@/lib/core/security/encryption', () => encryptionMock) +vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock) vi.mock('@/lib/credentials/workload-identity/issuer', () => ({ isWorkloadIdentityConfigured: async () => true, MAX_WORKLOAD_IDENTITY_TOKEN_TTL_SECONDS: 3600, @@ -19,6 +25,8 @@ vi.mock('@/lib/credentials/workload-identity/issuer', () => ({ })) import { credential } from '@sim/db/schema' +import { EgressRefusedError } from '@/lib/core/network/transport.server' +import { PayloadSizeLimitError } from '@/lib/core/utils/stream-limits' import { resolveDatabricksCredentialToken, verifyAndEncryptDatabricksCredential, @@ -27,6 +35,13 @@ import { WorkloadIdentityNotConfiguredError } from '@/lib/credentials/workload-i import { DATABRICKS_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' const owner = { workspaceId: 'ws-1', actorUserId: 'user-1' } +const secureFetch = inputValidationMockFns.mockSecureFetchWithValidation + +/** A guarded-fetch reply from the workspace's SCIM `Me` endpoint. */ +function scimReply(body: unknown, status = 200) { + const text = typeof body === 'string' ? body : JSON.stringify(body) + return { ok: status >= 200 && status < 300, status, text: async () => text } +} function patConfig(host: string) { return JSON.stringify({ authType: 'personal_access_token', host, token: 'dapi-secret' }) @@ -34,40 +49,73 @@ function patConfig(host: string) { describe('saving a Databricks connection', () => { const fetch = vi.fn() + const HOST = 'dbc-1234abcd-5678.cloud.databricks.com' beforeEach(() => { - fetch.mockReset().mockResolvedValue(jsonResponse({ userName: 'me@example.com' })) + fetch.mockReset() vi.stubGlobal('fetch', fetch) + secureFetch.mockReset().mockResolvedValue(scimReply({ userName: 'me@example.com' })) + encryptionMockFns.mockEncryptSecret.mockReset() encryptionMockFns.mockEncryptSecret.mockResolvedValue({ encrypted: 'ciphertext', iv: 'iv' }) }) - it('verifies a Databricks workspace host by calling only that host', async () => { + it('verifies through the egress guard, so the organization’s outbound route carries it', async () => { await expect( - verifyAndEncryptDatabricksCredential( - patConfig('dbc-1234abcd-5678.cloud.databricks.com'), - owner - ) + verifyAndEncryptDatabricksCredential(patConfig(HOST), owner) ).resolves.toMatchObject({ identity: 'me@example.com' }) - expect(fetch).toHaveBeenCalledWith( - 'https://dbc-1234abcd-5678.cloud.databricks.com/api/2.0/preview/scim/v2/Me', - expect.objectContaining({ redirect: 'error' }) + expect(secureFetch).toHaveBeenCalledWith( + `https://${HOST}/api/2.0/preview/scim/v2/Me`, + expect.objectContaining({ profile: 'configuredEndpoint', maxRedirects: 0 }) ) + expect(fetch).not.toHaveBeenCalled() }) it('refuses an identity reply over the size cap instead of saving the host as the identity', async () => { - fetch.mockResolvedValue( - jsonResponse({ userName: 'me@example.com', groups: 'g'.repeat(1024 * 1024) }) + secureFetch.mockRejectedValue( + new PayloadSizeLimitError({ label: 'response', maxBytes: 1024 * 1024 }) ) - await expect( - verifyAndEncryptDatabricksCredential( - patConfig('dbc-1234abcd-5678.cloud.databricks.com'), - owner - ) - ).rejects.toThrow(/Could not verify/) + await expect(verifyAndEncryptDatabricksCredential(patConfig(HOST), owner)).rejects.toThrow( + /Could not verify/ + ) expect(encryptionMockFns.mockEncryptSecret).not.toHaveBeenCalled() }) + it.each([ + [ + 'an IP access list', + { + error_code: '403', + message: + 'Source IP address: 203.0.113.9 is blocked by Databricks IP ACL for workspace: 1234', + }, + 'Source IP address: 203.0.113.9 is blocked by Databricks IP ACL for workspace: 1234', + ], + [ + 'a missing entitlement', + { detail: ' User is not entitled to access this workspace ', status: '403' }, + 'User is not entitled to access this workspace', + ], + ])( + 'names Databricks’ own reason when %s refuses the identity call', + async (_case, body, reason) => { + secureFetch.mockResolvedValue(scimReply(body, 403)) + + await expect(verifyAndEncryptDatabricksCredential(patConfig(HOST), owner)).rejects.toThrow( + `Could not verify the Databricks credential: workspace returned HTTP 403: ${reason}` + ) + expect(encryptionMockFns.mockEncryptSecret).not.toHaveBeenCalled() + } + ) + + it('reports a host the egress policy refuses as not allowed', async () => { + secureFetch.mockRejectedValue(new EgressRefusedError('url resolves to a private address')) + + await expect(verifyAndEncryptDatabricksCredential(patConfig(HOST), owner)).rejects.toThrow( + 'Could not verify the Databricks credential: the workspace host is not allowed' + ) + }) + it.each([ 'attacker.example.com', 'https://attacker.example.com', @@ -76,6 +124,7 @@ describe('saving a Databricks connection', () => { ])('refuses %s without sending the token anywhere', async (host) => { await expect(verifyAndEncryptDatabricksCredential(patConfig(host), owner)).rejects.toThrow() expect(fetch).not.toHaveBeenCalled() + expect(secureFetch).not.toHaveBeenCalled() expect(encryptionMockFns.mockEncryptSecret).not.toHaveBeenCalled() }) }) @@ -101,13 +150,14 @@ describe('saving a keyless Databricks connection', () => { ? jsonResponse({ access_token: 'dbx-token' }) : jsonResponse({ error: 'invalid_grant' }, probeStatus) } - return jsonResponse({ userName: 'sim-workflows' }) + throw new Error(`unexpected fetch ${url}`) }) } beforeEach(() => { fetch.mockReset() vi.stubGlobal('fetch', fetch) + secureFetch.mockReset().mockResolvedValue(scimReply({ userName: 'sim-workflows' })) encryptionMockFns.mockEncryptSecret.mockReset() encryptionMockFns.mockEncryptSecret.mockResolvedValue({ encrypted: 'ciphertext', iv: 'iv' }) }) @@ -200,7 +250,7 @@ describe('a keyless Databricks connection with its own audience', () => { ? jsonResponse({ access_token: 'dbx-token' }) : jsonResponse({ error: 'invalid_grant' }, 401) } - return jsonResponse({ userName: 'sim-workflows' }) + throw new Error(`unexpected fetch ${url}`) }) } @@ -216,6 +266,7 @@ describe('a keyless Databricks connection with its own audience', () => { beforeEach(() => { fetch.mockReset() vi.stubGlobal('fetch', fetch) + secureFetch.mockReset().mockResolvedValue(scimReply({ userName: 'sim-workflows' })) hoisted.mint.mockClear() encryptionMockFns.mockEncryptSecret.mockReset() encryptionMockFns.mockEncryptSecret.mockResolvedValue({ encrypted: 'ciphertext', iv: 'iv' }) diff --git a/apps/sim/lib/credentials/databricks-service-account.server.ts b/apps/sim/lib/credentials/databricks-service-account.server.ts index 9ac606acb4a..be27b8670d2 100644 --- a/apps/sim/lib/credentials/databricks-service-account.server.ts +++ b/apps/sim/lib/credentials/databricks-service-account.server.ts @@ -3,8 +3,10 @@ import { credential } from '@sim/db/schema' import { getErrorMessage } from '@sim/utils/errors' import { eq } from 'drizzle-orm' import { z } from 'zod' +import { EgressRefusedError } from '@/lib/core/network/transport.server' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { validateDatabricksWorkspaceHost } from '@/lib/core/security/input-validation' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits' import { failureClause, parseServiceAccountConfig } from '@/lib/credentials/service-account-config' import { @@ -284,31 +286,64 @@ function bindSubject( }) } -/** Proves a configuration by calling the workspace's SCIM `Me` with the resolved token. */ +/** Longest Databricks refusal reason carried into a verification error. */ +const MAX_REFUSAL_REASON_LENGTH = 300 + +/** + * Reads the reason from a Databricks error body: REST APIs answer + * `{ "error_code", "message" }` and SCIM answers `{ "detail", "status" }`. + * Returns `undefined` for a body that is not JSON or names no string reason. + */ +function databricksRefusalReason(body: string): string | undefined { + let payload: unknown + try { + payload = JSON.parse(body) + } catch { + return undefined + } + if (!payload || typeof payload !== 'object') return undefined + const { message, detail } = payload as { message?: unknown; detail?: unknown } + const reason = typeof message === 'string' ? message : typeof detail === 'string' ? detail : '' + return reason.trim().slice(0, MAX_REFUSAL_REASON_LENGTH) || undefined +} + +/** + * Proves a configuration by calling the workspace's SCIM `Me` with the resolved + * token. The call goes through the egress guard, which also routes it through + * the organization's outbound gateway, so a workspace whose IP access list + * allows only the organization's dedicated egress addresses accepts it. + */ async function verifyWorkspaceIdentity( config: DatabricksCredentialConfig, owner: WorkloadIdentityOwner ): Promise { try { const token = await resolveDatabricksAccessToken(config, owner) - const response = await fetch(`${workspaceOrigin(config.host)}/api/2.0/preview/scim/v2/Me`, { - headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, - redirect: 'error', - signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), - }) - if (!response.ok) throw new Error(`workspace returned HTTP ${response.status}`) - // A malformed body only loses the display name; an oversized one must fail - // verification rather than save the host as the identity. - const me = (await readResponseJsonWithLimit(response, { - maxBytes: MAX_RESPONSE_BYTES, - label: 'Databricks identity response', - }).catch((error: unknown) => { - if (error instanceof SyntaxError) return {} - throw error - })) as { - userName?: unknown - displayName?: unknown + const response = await secureFetchWithValidation( + `${workspaceOrigin(config.host)}/api/2.0/preview/scim/v2/Me`, + { + profile: 'configuredEndpoint', + headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, + // `timeout` resets on activity; the signal caps the whole call, DNS included. + timeout: REQUEST_TIMEOUT_MS, + signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), + maxRedirects: 0, + // An oversized body rejects here and fails verification rather than + // saving the host as the identity. + maxResponseBytes: MAX_RESPONSE_BYTES, + logUrlValidationDetails: false, + } + ) + const body = await response.text() + if (!response.ok) { + const reason = databricksRefusalReason(body) + throw new Error(`workspace returned HTTP ${response.status}${reason ? `: ${reason}` : ''}`) } + // A malformed body only loses the display name. + let me: { userName?: unknown; displayName?: unknown } = {} + try { + me = JSON.parse(body) ?? {} + } catch {} return ( (typeof me.userName === 'string' && me.userName) || (typeof me.displayName === 'string' && me.displayName) || @@ -317,6 +352,11 @@ async function verifyWorkspaceIdentity( } catch (error) { // A keyless refusal already names its cause and code; wrapping it would lose both. if (error instanceof KeylessCredentialRefusedError) throw error + if (error instanceof EgressRefusedError) { + throw new DatabricksCredentialError( + 'Could not verify the Databricks credential: the workspace host is not allowed' + ) + } throw new DatabricksCredentialError( `Could not verify the Databricks credential: ${getErrorMessage(error, 'Unknown error')}` ) diff --git a/apps/sim/lib/credentials/google-workload-identity.server.test.ts b/apps/sim/lib/credentials/google-workload-identity.server.test.ts index 07db28f217f..4fcfbc78b84 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.test.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.test.ts @@ -383,6 +383,27 @@ describe('saving a keyless Google Cloud credential', () => { expect(error.message).not.toContain('fed:') } }) + + it('names a VPC Service Controls perimeter instead of asking for an IAM role it already has', async () => { + fakeGoogle(fetch, { + iamError: jsonResponse( + { + error: { + code: 403, + status: 'PERMISSION_DENIED', + message: + "Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: abc123", + }, + }, + 403 + ), + }) + + const error: Error = await save({}).catch((caught) => caught) + + expect(error.message).toContain('VPC Service Controls perimeter') + expect(error.message).not.toContain('Grant') + }) }) describe('getting a keyless Google Cloud token for a run', () => { diff --git a/apps/sim/lib/credentials/google-workload-identity.server.ts b/apps/sim/lib/credentials/google-workload-identity.server.ts index a70368edb1f..31af85ea02f 100644 --- a/apps/sim/lib/credentials/google-workload-identity.server.ts +++ b/apps/sim/lib/credentials/google-workload-identity.server.ts @@ -380,16 +380,30 @@ async function exchangeSubjectToken( return payload.access_token } +/** + * Whether Google refused the call under a VPC Service Controls perimeter: the + * error names a `vpcServiceControlsUniqueIdentifier` or a `VPC_SERVICE_CONTROLS` + * violation. Granting IAM roles cannot fix that, so it gets its own message. + * @see https://cloud.google.com/vpc-service-controls/docs/troubleshooting + */ +function isVpcServiceControlsDenial(payload: Record): boolean { + return /vpcServiceControlsUniqueIdentifier|VPC_SERVICE_CONTROLS/.test(JSON.stringify(payload)) +} + /** * Copy for an IAM Credentials refusal. Signing needs a role the impersonation * role lacks, so a refused signature always names the token-creator role. */ function iamFailureMessage( status: number, + payload: Record, config: GoogleWorkloadIdentityConfig, action: 'impersonate' | 'sign' ) { if (isProviderOutage(status)) return 'Google Cloud IAM is unavailable. Try again.' + if (status === 403 && isVpcServiceControlsDenial(payload)) { + return 'A VPC Service Controls perimeter in your Google Cloud organization blocked this request. Ask an administrator to allow the IAM Service Account Credentials API for this service account through the perimeter, for example with an ingress rule for this workload identity pool.' + } if (status === 403) { const refusal = action === 'sign' @@ -424,7 +438,10 @@ async function impersonateServiceAccount( { Authorization: `Bearer ${federatedToken}` } ) if (status !== 200) { - throw new GoogleWorkloadIdentityError(iamFailureMessage(status, config, 'impersonate'), status) + throw new GoogleWorkloadIdentityError( + iamFailureMessage(status, payload, config, 'impersonate'), + status + ) } if (typeof payload.accessToken !== 'string' || !payload.accessToken) { throw new GoogleWorkloadIdentityError('Google Cloud IAM answered without a token.', 502) @@ -473,7 +490,10 @@ async function signDelegationAssertion( { Authorization: `Bearer ${federatedToken}` } ) if (status !== 200) { - throw new GoogleWorkloadIdentityError(iamFailureMessage(status, config, 'sign'), status) + throw new GoogleWorkloadIdentityError( + iamFailureMessage(status, payload, config, 'sign'), + status + ) } if (typeof payload.signedJwt !== 'string' || !payload.signedJwt) { throw new GoogleWorkloadIdentityError('Google Cloud IAM answered without a signature.', 502) diff --git a/apps/sim/lib/credentials/microsoft-app-service-account.server.test.ts b/apps/sim/lib/credentials/microsoft-app-service-account.server.test.ts index 9fe5f1bed7c..7b2c5b34075 100644 --- a/apps/sim/lib/credentials/microsoft-app-service-account.server.test.ts +++ b/apps/sim/lib/credentials/microsoft-app-service-account.server.test.ts @@ -508,6 +508,28 @@ describe('saving a Microsoft app credential', () => { }) }) + it('names a Conditional Access block instead of a federation mismatch', async () => { + fakeMicrosoft(fetch, { + tokenError: jsonResponse( + { + error: 'invalid_grant', + error_codes: [53003], + error_description: 'AADSTS53003: Access has been blocked by Conditional Access policies.', + }, + 400 + ), + }) + + const grant = issueWorkloadIdentitySubject('microsoft', { workspaceId: 'ws-1' }, 'user-1') + const error: Error = await save({ + authType: 'federated', + subject: grant.subject, + subjectProof: grant.subjectProof, + }).catch((caught) => caught) + + expect(error.message).toContain('Conditional Access policy') + }) + it('never repeats the secret, key, or assertion in a refusal', async () => { fakeMicrosoft(fetch, { tokenError: jsonResponse( diff --git a/apps/sim/lib/credentials/microsoft-app-service-account.server.ts b/apps/sim/lib/credentials/microsoft-app-service-account.server.ts index 9a56cb309da..1a37df45dcd 100644 --- a/apps/sim/lib/credentials/microsoft-app-service-account.server.ts +++ b/apps/sim/lib/credentials/microsoft-app-service-account.server.ts @@ -231,7 +231,8 @@ function federationMismatchMessage(config: MicrosoftAppConfig): string { * AADSTS70021 (no matching federated identity record) is documented in the * workload identity federation considerations, and AADSTS700213 (no matching * record for the presented subject, issuer, or audience) in the August 2024 - * breaking-changes entry; any other keyless refusal still names the mismatch. + * breaking-changes entry; AADSTS53003 is a Conditional Access block; any other keyless + * refusal still names the mismatch. * @see https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes * @see https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-considerations * @see https://learn.microsoft.com/en-us/entra/identity-platform/reference-breaking-changes @@ -250,6 +251,8 @@ function entraFailureMessage(aadsts: number | undefined, config: MicrosoftAppCon return 'Microsoft Entra could not verify the signed assertion. Upload this certificate (the .cer or .pem public part) to the app registration’s Certificates tab.' case 7000112: return 'This app registration is disabled in the tenant.' + case 53003: + return 'A Conditional Access policy in this tenant blocked the sign-in. Ask a tenant admin to exclude this app from the policy, such as a location-based policy for workload identities.' case 500011: return config.environmentUrl ? 'Microsoft Entra does not recognize this Dataverse environment in the tenant. Check the environment URL.' diff --git a/apps/sim/lib/credentials/orchestration/credential-create.ts b/apps/sim/lib/credentials/orchestration/credential-create.ts index 8ea4fc7ac18..222b505c8ee 100644 --- a/apps/sim/lib/credentials/orchestration/credential-create.ts +++ b/apps/sim/lib/credentials/orchestration/credential-create.ts @@ -29,6 +29,7 @@ import { } from '@/lib/credentials/service-account-secret' import { isTokenServiceAccountProviderId } from '@/lib/credentials/token-service-accounts/descriptors' import { TokenServiceAccountValidationError } from '@/lib/credentials/token-service-accounts/errors' +import { releasePendingWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/pending-subjects' import { assertWorkloadIdentitySubjectUnclaimed, KeylessCredentialRefusedError, @@ -566,6 +567,9 @@ export async function createCredentialRecord( createdAt: now, updatedAt: now, }) + if (resolvedWorkloadIdentitySubject) { + await releasePendingWorkloadIdentitySubject(tx, resolvedWorkloadIdentitySubject) + } if ((type === 'env_workspace' || type === 'service_account') && currentContext.ownerId) { for (const memberUserId of currentContext.memberUserIds) { diff --git a/apps/sim/lib/credentials/orchestration/index.ts b/apps/sim/lib/credentials/orchestration/index.ts index 5f1dada0b04..70ab4e4d3d6 100644 --- a/apps/sim/lib/credentials/orchestration/index.ts +++ b/apps/sim/lib/credentials/orchestration/index.ts @@ -54,6 +54,7 @@ import { SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID, } from '@/lib/credentials/token-service-accounts/descriptors' import { TokenServiceAccountValidationError } from '@/lib/credentials/token-service-accounts/errors' +import { releasePendingWorkloadIdentitySubject } from '@/lib/credentials/workload-identity/pending-subjects' import { assertReconnectedSubjectUnclaimed, KeylessCredentialRefusedError, @@ -560,12 +561,13 @@ export async function updateCredentialRecord( const rotatedSecret = rotatedKeylessSecret try { await db.transaction(async (tx) => { - await assertReconnectedSubjectUnclaimed(tx, { + const subject = await assertReconnectedSubjectUnclaimed(tx, { rotatedSecret, scope: resourceScopeFromOwner(params.credential), credentialId: params.credentialId, }) await tx.update(credential).set(updates).where(eq(credential.id, params.credentialId)) + if (subject) await releasePendingWorkloadIdentitySubject(tx, subject) }) } catch (error) { if (error instanceof KeylessCredentialRefusedError) return keylessFailure(error) diff --git a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts index 5f145ec5990..40e9e464a84 100644 --- a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts +++ b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts @@ -240,6 +240,7 @@ export const ROOTLY_SERVICE_ACCOUNT_PROVIDER_ID = 'rootly-service-account' as co export const RESEND_SERVICE_ACCOUNT_PROVIDER_ID = 'resend-service-account' as const export const PAGERDUTY_SERVICE_ACCOUNT_PROVIDER_ID = 'pagerduty-service-account' as const export const LEMLIST_SERVICE_ACCOUNT_PROVIDER_ID = 'lemlist-service-account' as const +export const MONACO_SERVICE_ACCOUNT_PROVIDER_ID = 'monaco-service-account' as const export const GRANOLA_SERVICE_ACCOUNT_PROVIDER_ID = 'granola-service-account' as const export const GRAIN_SERVICE_ACCOUNT_PROVIDER_ID = 'grain-service-account' as const export const FATHOM_SERVICE_ACCOUNT_PROVIDER_ID = 'fathom-service-account' as const @@ -378,6 +379,7 @@ export type TokenServiceAccountProviderId = | typeof RESEND_SERVICE_ACCOUNT_PROVIDER_ID | typeof PAGERDUTY_SERVICE_ACCOUNT_PROVIDER_ID | typeof LEMLIST_SERVICE_ACCOUNT_PROVIDER_ID + | typeof MONACO_SERVICE_ACCOUNT_PROVIDER_ID | typeof GRANOLA_SERVICE_ACCOUNT_PROVIDER_ID | typeof GRAIN_SERVICE_ACCOUNT_PROVIDER_ID | typeof FATHOM_SERVICE_ACCOUNT_PROVIDER_ID @@ -1854,6 +1856,27 @@ export const TOKEN_SERVICE_ACCOUNT_DESCRIPTORS: Record< helpText: 'Create a personal or workspace access token in Grain under Settings > Integrations > API. A personal token has the same access you have in Grain.', }, + [MONACO_SERVICE_ACCOUNT_PROVIDER_ID]: { + providerId: MONACO_SERVICE_ACCOUNT_PROVIDER_ID, + serviceLabel: 'Monaco', + tokenNoun: 'API key', + connectNoun: 'API key', + fields: [ + { + id: 'apiToken', + label: 'API key', + placeholder: 'mks_...', + secret: true, + hintPattern: /^mks_/, + hintMessage: 'Monaco API keys start with mks_.', + }, + ], + docsUrl: 'https://docs.sim.ai/integrations/monaco', + helpText: + 'Create an API key in Monaco under Settings > API Keys. Monaco shows the key once, so copy it before closing the dialog. The key acts as the user it is tied to, or as the user who created it.', + invalidCredentialsHelp: + 'Monaco rejected this key. Check that it starts with mks_ and has not been deactivated in Settings > API Keys.', + }, [GRANOLA_SERVICE_ACCOUNT_PROVIDER_ID]: { providerId: GRANOLA_SERVICE_ACCOUNT_PROVIDER_ID, serviceLabel: 'Granola', diff --git a/apps/sim/lib/credentials/token-service-accounts/server.ts b/apps/sim/lib/credentials/token-service-accounts/server.ts index ffb26b19c4a..ccb9fa869e4 100644 --- a/apps/sim/lib/credentials/token-service-accounts/server.ts +++ b/apps/sim/lib/credentials/token-service-accounts/server.ts @@ -78,6 +78,7 @@ import { MAILCHIMP_SERVICE_ACCOUNT_PROVIDER_ID, MAILGUN_SERVICE_ACCOUNT_PROVIDER_ID, MEM0_SERVICE_ACCOUNT_PROVIDER_ID, + MONACO_SERVICE_ACCOUNT_PROVIDER_ID, MONDAY_SERVICE_ACCOUNT_PROVIDER_ID, NEW_RELIC_SERVICE_ACCOUNT_PROVIDER_ID, NOTION_SERVICE_ACCOUNT_PROVIDER_ID, @@ -215,6 +216,7 @@ import { validateMailchimpServiceAccount, validateMailgunServiceAccount, validateMem0ServiceAccount, + validateMonacoServiceAccount, validateNewRelicServiceAccount, validateOktaServiceAccount, validateOtterServiceAccount, @@ -356,6 +358,7 @@ const TOKEN_SERVICE_ACCOUNT_VALIDATORS: Record< [DISCORD_SERVICE_ACCOUNT_PROVIDER_ID]: validateDiscordServiceAccount, [ZEP_SERVICE_ACCOUNT_PROVIDER_ID]: validateZepServiceAccount, [MEM0_SERVICE_ACCOUNT_PROVIDER_ID]: validateMem0ServiceAccount, + [MONACO_SERVICE_ACCOUNT_PROVIDER_ID]: validateMonacoServiceAccount, [YOUTUBE_SERVICE_ACCOUNT_PROVIDER_ID]: validateYouTubeServiceAccount, [YOUCOM_SERVICE_ACCOUNT_PROVIDER_ID]: validateYouComServiceAccount, [UPTIMEROBOT_SERVICE_ACCOUNT_PROVIDER_ID]: validateUptimeRobotServiceAccount, diff --git a/apps/sim/lib/credentials/token-service-accounts/validators/token-checks.ts b/apps/sim/lib/credentials/token-service-accounts/validators/token-checks.ts index afe102f6dd4..9d6d5bbb68a 100644 --- a/apps/sim/lib/credentials/token-service-accounts/validators/token-checks.ts +++ b/apps/sim/lib/credentials/token-service-accounts/validators/token-checks.ts @@ -45,6 +45,7 @@ import { } from '@/tools/logfire/utils' import type { MailgunRegion } from '@/tools/mailgun/types' import { getMailgunApiBaseUrl } from '@/tools/mailgun/utils' +import { MONACO_API_BASE, monacoHeaders } from '@/tools/monaco/utils' import { getNerdGraphEndpoint, NEW_RELIC_REGIONS, newRelicHeaders } from '@/tools/new_relic/utils' import { oktaHeaders } from '@/tools/okta/utils' import { planeApiUrl, planeHeaders } from '@/tools/plane/utils' @@ -1337,6 +1338,24 @@ export const validateMem0ServiceAccount = tokenCheckValidator({ }), }) +/** + * Monaco's get-authenticated-user endpoint answers 200 with the key's organization for a valid + * `mks_` key, 401 for a malformed bearer token, and 403 for an unknown or deactivated key. + */ +export const validateMonacoServiceAccount = tokenCheckValidator({ + serviceLabel: 'Monaco', + request: ({ apiToken }) => ({ + url: `${MONACO_API_BASE}/v1/me`, + headers: monacoHeaders(apiToken), + }), + acceptsBody: (body) => { + const orgName = toRecord(toRecord(body).data).org_name + return typeof orgName === 'string' && orgName.trim() + ? { displayName: `Monaco (${orgName.trim()})` } + : true + }, +}) + /** * The NerdGraph error classes New Relic's own Go client retries: NerdGraph failing to * answer, not a verdict on the key. diff --git a/apps/sim/lib/credentials/workload-identity/pending-subjects.ts b/apps/sim/lib/credentials/workload-identity/pending-subjects.ts new file mode 100644 index 00000000000..4eee6549ee7 --- /dev/null +++ b/apps/sim/lib/credentials/workload-identity/pending-subjects.ts @@ -0,0 +1,199 @@ +import { db } from '@sim/db' +import { workloadIdentityPendingSubject } from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { and, desc, eq, gt, inArray, lte } from 'drizzle-orm' +import type { WorkloadIdentityProvider } from '@/lib/credentials/workload-identity/audiences' +import { + isWorkloadIdentitySubjectHeld, + lockWorkloadIdentitySubject, + newWorkloadIdentitySubject, + WorkloadIdentitySubjectError, +} from '@/lib/credentials/workload-identity/subject' +import { acquireAdvisoryXactLock } from '@/lib/db/advisory-locks' +import type { DbTransaction } from '@/lib/db/types' + +/** + * How long Sim holds an unsaved keyless subject for the person it was issued to. + * Each time the subject is shown or its proof renewed, the hold restarts, so a + * trust policy that takes days of review can still be saved against it. + */ +export const PENDING_SUBJECT_HOLD_MS = 30 * 24 * 60 * 60 * 1000 + +/** Most pending subjects one person keeps per workspace and provider; the least recently shown are dropped. */ +const MAX_PENDING_SUBJECTS_PER_ISSUER = 20 + +/** Who a pending subject is held for. */ +export interface PendingSubjectIssuer { + provider: WorkloadIdentityProvider + workspaceId: string + userId: string +} + +/** + * Which subject to hand out: + * - `latest`: the newest subject still held for this person, else a new one (the connect dialog). + * - `new`: always a new one (the API's default, so scripts setting up several credentials get one each). + * - `renew`: exactly `subject`, which must still be held for this person. + */ +export type PendingSubjectRequest = + | { kind: 'latest' } + | { kind: 'new' } + | { kind: 'renew'; subject: string } + +/** A subject Sim holds for one person until a credential saves it. */ +export interface PendingSubject { + subject: string + /** When Sim first issued the subject. */ + issuedAt: Date + /** Until when Sim keeps offering the subject if no credential saves it. */ + heldUntil: Date + /** Whether the subject was issued earlier rather than by this request. */ + reused: boolean +} + +function issuerCondition(issuer: PendingSubjectIssuer) { + return and( + eq(workloadIdentityPendingSubject.workspaceId, issuer.workspaceId), + eq(workloadIdentityPendingSubject.userId, issuer.userId), + eq(workloadIdentityPendingSubject.provider, issuer.provider) + ) +} + +async function mintPendingSubject( + tx: DbTransaction, + issuer: PendingSubjectIssuer, + now: Date +): Promise { + const heldUntil = new Date(now.getTime() + PENDING_SUBJECT_HOLD_MS) + const subject = newWorkloadIdentitySubject({ workspaceId: issuer.workspaceId }) + await tx.insert(workloadIdentityPendingSubject).values({ + id: generateId(), + workspaceId: issuer.workspaceId, + userId: issuer.userId, + provider: issuer.provider, + subject, + createdAt: now, + expiresAt: heldUntil, + }) + const kept = await tx + .select({ id: workloadIdentityPendingSubject.id }) + .from(workloadIdentityPendingSubject) + .where(issuerCondition(issuer)) + .orderBy(desc(workloadIdentityPendingSubject.expiresAt)) + .offset(MAX_PENDING_SUBJECTS_PER_ISSUER) + if (kept.length > 0) { + await tx.delete(workloadIdentityPendingSubject).where( + inArray( + workloadIdentityPendingSubject.id, + kept.map((row) => row.id) + ) + ) + } + return { subject, issuedAt: now, heldUntil, reused: false } +} + +/** + * Restarts the hold on a still-pending subject, or drops it when a credential already + * holds it: a deployment that predates releasing pending subjects leaves the row + * behind after a save, and a subject in use is never offered again. + */ +async function renewIfUnclaimed( + tx: DbTransaction, + issuer: PendingSubjectIssuer, + row: { id: string; subject: string; createdAt: Date }, + now: Date +): Promise { + // Waits out a save of this subject still in flight, so the check below sees it. + await lockWorkloadIdentitySubject(tx, row.subject) + const held = await isWorkloadIdentitySubjectHeld(tx, { + subject: row.subject, + scope: { kind: 'workspace', workspaceId: issuer.workspaceId }, + }) + if (held) { + await tx + .delete(workloadIdentityPendingSubject) + .where(eq(workloadIdentityPendingSubject.id, row.id)) + return null + } + const heldUntil = new Date(now.getTime() + PENDING_SUBJECT_HOLD_MS) + // A save that claimed the subject meanwhile has already deleted the row. + const renewed = await tx + .update(workloadIdentityPendingSubject) + .set({ expiresAt: heldUntil }) + .where(eq(workloadIdentityPendingSubject.id, row.id)) + .returning({ id: workloadIdentityPendingSubject.id }) + if (renewed.length === 0) return null + return { subject: row.subject, issuedAt: row.createdAt, heldUntil, reused: true } +} + +/** + * The keyless subject to show this person, held for them until a credential saves + * it or {@link PENDING_SUBJECT_HOLD_MS} passes without it being shown again. + * Requests for one person, workspace, and provider run one at a time, so two + * dialogs opened together do not each mint a subject. + */ +export async function resolvePendingWorkloadIdentitySubject( + issuer: PendingSubjectIssuer, + request: PendingSubjectRequest, + now: Date = new Date() +): Promise { + const pending = await db.transaction(async (tx): Promise => { + await acquireAdvisoryXactLock( + tx, + 'workload_identity_pending_subject', + `workload-identity-pending-subject:${issuer.workspaceId}:${issuer.userId}:${issuer.provider}` + ) + await tx + .delete(workloadIdentityPendingSubject) + .where(and(issuerCondition(issuer), lte(workloadIdentityPendingSubject.expiresAt, now))) + + if (request.kind === 'new') return mintPendingSubject(tx, issuer, now) + + const candidates = await tx + .select({ + id: workloadIdentityPendingSubject.id, + subject: workloadIdentityPendingSubject.subject, + createdAt: workloadIdentityPendingSubject.createdAt, + }) + .from(workloadIdentityPendingSubject) + .where( + and( + issuerCondition(issuer), + gt(workloadIdentityPendingSubject.expiresAt, now), + request.kind === 'renew' + ? eq(workloadIdentityPendingSubject.subject, request.subject) + : undefined + ) + ) + .orderBy(desc(workloadIdentityPendingSubject.createdAt)) + + for (const row of candidates) { + const renewed = await renewIfUnclaimed(tx, issuer, row, now) + if (renewed) return renewed + } + // A refused renewal still commits the cleanup above, so it is reported after the transaction. + if (request.kind === 'renew') return null + return mintPendingSubject(tx, issuer, now) + }) + if (!pending) { + throw new WorkloadIdentitySubjectError( + 'Sim is not holding this subject for you: it was issued to someone else, a credential already uses it, or it expired. Request a new subject and trust it instead.', + 'WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING' + ) + } + return pending +} + +/** + * Stops holding `subject` once a credential saves it, inside the transaction that + * writes the credential, so a subject in use is never offered again, even after + * that credential is deleted. + */ +export async function releasePendingWorkloadIdentitySubject( + tx: DbTransaction, + subject: string +): Promise { + await tx + .delete(workloadIdentityPendingSubject) + .where(eq(workloadIdentityPendingSubject.subject, subject)) +} diff --git a/apps/sim/lib/credentials/workload-identity/subject.test.ts b/apps/sim/lib/credentials/workload-identity/subject.test.ts index 079ebf8fbff..83a1f83fb8f 100644 --- a/apps/sim/lib/credentials/workload-identity/subject.test.ts +++ b/apps/sim/lib/credentials/workload-identity/subject.test.ts @@ -11,6 +11,7 @@ import { assertWorkloadIdentitySubjectUnclaimed, bindWorkloadIdentitySubject, issueWorkloadIdentitySubject, + signWorkloadIdentitySubject, verifyWorkloadIdentitySubject, WorkloadIdentitySubjectError, } from '@/lib/credentials/workload-identity/subject' @@ -55,6 +56,14 @@ describe('workload identity subjects', () => { ).toThrow(WorkloadIdentitySubjectError) }) + it('refuses to sign a proof for another workspace’s subject', () => { + const elsewhere = issueWorkloadIdentitySubject('aws', { workspaceId: 'ws-2' }, 'user-1', now) + + expect(() => + signWorkloadIdentitySubject('aws', owner, 'user-1', elsewhere.subject, now) + ).toThrow(expect.objectContaining({ detailCode: 'WORKLOAD_IDENTITY_PROOF_INVALID' })) + }) + it('refuses a proof presented in another workspace', () => { const grant = issueWorkloadIdentitySubject('aws', owner, 'user-1', now) diff --git a/apps/sim/lib/credentials/workload-identity/subject.ts b/apps/sim/lib/credentials/workload-identity/subject.ts index 8ca02e20176..a5af315f9e4 100644 --- a/apps/sim/lib/credentials/workload-identity/subject.ts +++ b/apps/sim/lib/credentials/workload-identity/subject.ts @@ -60,6 +60,8 @@ type KeylessCredentialErrorCode = | 'WORKLOAD_IDENTITY_SUBJECT_IN_USE' /** A sibling credential could not be read, so the subject's uniqueness is unknown. */ | 'WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE' + /** The subject to renew is not one Sim holds for this person: never issued to them, saved, or expired. */ + | 'WORKLOAD_IDENTITY_SUBJECT_NOT_PENDING' /** Refusals caused by another credential's state rather than by the request itself. */ const KEYLESS_CONFLICT_CODES: ReadonlySet = new Set([ @@ -176,7 +178,28 @@ export function issueWorkloadIdentitySubject( userId: string, now: number = Date.now() ): WorkloadIdentitySubjectGrant { - const subject = newWorkloadIdentitySubject(owner) + return signWorkloadIdentitySubject( + provider, + owner, + userId, + newWorkloadIdentitySubject(owner), + now + ) +} + +/** + * Signs a proof that lets the given user attach `subject`, which Sim already issued + * to them, to a new credential of one provider. Callers own the decision that the + * subject is theirs to attach. + */ +export function signWorkloadIdentitySubject( + provider: WorkloadIdentityProvider, + owner: WorkloadIdentityOwner, + userId: string, + subject: string, + now: number = Date.now() +): WorkloadIdentitySubjectGrant { + assertWorkloadIdentitySubjectOwner(subject, owner) const expiresAt = now + SUBJECT_PROOF_TTL_MS return { subject, @@ -276,7 +299,12 @@ async function readStoredSubject(encrypted: string): Promise { +/** + * Holds `subject` for the rest of the transaction. Every write that saves a + * subject takes it first, so a reader that takes it too sees a concurrent save + * once that save commits. + */ +export function lockWorkloadIdentitySubject(tx: DbTransaction, subject: string): Promise { return acquireAdvisoryXactLock( tx, 'workload_identity_subject', @@ -289,11 +317,14 @@ function scopeNoun(scope: ResourceScope): string { return scope.kind === 'workspace' ? 'this workspace' : 'this organization' } -/** Throws unless no live keyless credential of the owner other than `credentialId` holds `subject`. */ -async function assertNoOtherCredentialHolds( +/** + * Whether a live keyless credential of the owner other than `credentialId` holds + * `subject`: `unverifiable` when a sibling's secret cannot be read, so it might. + */ +async function subjectHolding( tx: DbTransaction, input: { subject: string; scope: ResourceScope; credentialId?: string } -): Promise { +): Promise<'free' | 'held' | 'unverifiable'> { const rows = await tx .select({ id: credential.id, @@ -308,25 +339,51 @@ async function assertNoOtherCredentialHolds( input.credentialId ? ne(credential.id, input.credentialId) : undefined ) ) + let unverifiable = false for (const row of rows) { if (!row.encryptedServiceAccountKey) continue const storedSubject = await readStoredSubject(row.encryptedServiceAccountKey) + if (storedSubject === input.subject) return 'held' if (storedSubject === null) { logger.warn('Unreadable keyless credential blocks a subject uniqueness check', { credentialId: row.id, }) - throw new WorkloadIdentitySubjectError( - `Sim could not read another keyless credential in ${scopeNoun(input.scope)}, so it cannot confirm this trust setup is unused. Ask an admin to reconnect or delete the broken keyless credential, then try again.`, - 'WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE' - ) - } - if (storedSubject === input.subject) { - throw new WorkloadIdentitySubjectError( - `Another credential in ${scopeNoun(input.scope)} already uses this trust setup. Start the keyless connection again.`, - 'WORKLOAD_IDENTITY_SUBJECT_IN_USE' - ) + unverifiable = true } } + return unverifiable ? 'unverifiable' : 'free' +} + +/** Throws unless no live keyless credential of the owner other than `credentialId` holds `subject`. */ +async function assertNoOtherCredentialHolds( + tx: DbTransaction, + input: { subject: string; scope: ResourceScope; credentialId?: string } +): Promise { + const holding = await subjectHolding(tx, input) + if (holding === 'unverifiable') { + throw new WorkloadIdentitySubjectError( + `Sim could not read another keyless credential in ${scopeNoun(input.scope)}, so it cannot confirm this trust setup is unused. Ask an admin to reconnect or delete the broken keyless credential, then try again.`, + 'WORKLOAD_IDENTITY_SUBJECT_UNVERIFIABLE' + ) + } + if (holding === 'held') { + throw new WorkloadIdentitySubjectError( + `Another credential in ${scopeNoun(input.scope)} already uses this trust setup. Start the keyless connection again.`, + 'WORKLOAD_IDENTITY_SUBJECT_IN_USE' + ) + } +} + +/** + * Whether a live keyless credential of the owner holds `subject`. A sibling whose + * secret cannot be read does not count: saving still refuses the subject as + * unverifiable, and it stays usable once that sibling is fixed. + */ +export async function isWorkloadIdentitySubjectHeld( + tx: DbTransaction, + input: { subject: string; scope: ResourceScope } +): Promise { + return (await subjectHolding(tx, input)) === 'held' } /** @@ -353,7 +410,7 @@ export async function assertWorkloadIdentitySubjectUnclaimed( tx: DbTransaction, input: { subject: string; scope: ResourceScope } ): Promise { - await lockSubject(tx, input.subject) + await lockWorkloadIdentitySubject(tx, input.subject) await assertNoOtherCredentialHolds(tx, input) } @@ -368,15 +425,15 @@ export async function assertWorkloadIdentitySubjectUnclaimed( * could have claimed the old one, so writing it back unchecked would leave two * credentials holding it. Locks the subject first, then the credential row; no * path takes a subject lock while holding a credential row, so the order cannot - * deadlock. + * deadlock. Returns the subject the rotated secret holds, if any. */ export async function assertReconnectedSubjectUnclaimed( tx: DbTransaction, input: { rotatedSecret: string; scope: ResourceScope; credentialId: string } -): Promise { +): Promise { const subject = await readStoredSubject(input.rotatedSecret) - if (!subject) return - await lockSubject(tx, subject) + if (!subject) return undefined + await lockWorkloadIdentitySubject(tx, subject) const [current] = await tx .select({ encryptedServiceAccountKey: credential.encryptedServiceAccountKey }) .from(credential) @@ -385,10 +442,11 @@ export async function assertReconnectedSubjectUnclaimed( const currentSubject = current?.encryptedServiceAccountKey ? await readStoredSubject(current.encryptedServiceAccountKey) : undefined - if (currentSubject === subject) return + if (currentSubject === subject) return subject await assertNoOtherCredentialHolds(tx, { subject, scope: input.scope, credentialId: input.credentialId, }) + return subject } diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 93ff38e9f9e..8bc677aa3a9 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -173,6 +173,7 @@ import { MintlifyIcon, MistralIcon, ModalIcon, + MonacoIcon, MondayIcon, MongoDBIcon, MySQLIcon, @@ -563,6 +564,7 @@ export const blockTypeToIconMap: Record = { mistral_parse: MistralIcon, mistral_parse_v3: MistralIcon, modal: ModalIcon, + monaco: MonacoIcon, monday: MondayIcon, mongodb: MongoDBIcon, mssql: MicrosoftSqlIcon, diff --git a/apps/sim/lib/knowledge/mcp/server.ts b/apps/sim/lib/knowledge/mcp/server.ts index 096e896134f..b4dc2b4aa2c 100644 --- a/apps/sim/lib/knowledge/mcp/server.ts +++ b/apps/sim/lib/knowledge/mcp/server.ts @@ -316,7 +316,12 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe } catch (error) { signal.throwIfAborted() const message = await callerErrorMessage(error) - if (!message) + if (message) + logger.info('Knowledge MCP batched read returned an error', { + operation: knowledgeOperations.readDocument.id, + reason: message, + }) + else logger.error('Knowledge MCP batched read failed', { operation: knowledgeOperations.readDocument.id, error, diff --git a/apps/sim/lib/knowledge/mcp/tool-arguments.test.ts b/apps/sim/lib/knowledge/mcp/tool-arguments.test.ts new file mode 100644 index 00000000000..bca0b850c9f --- /dev/null +++ b/apps/sim/lib/knowledge/mcp/tool-arguments.test.ts @@ -0,0 +1,28 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { toolCallInputIssues } from '@/lib/knowledge/mcp/tool-arguments' + +const searchCall = (args: Record) => ({ + jsonrpc: '2.0', + id: 1, + method: 'tools/call', + params: { name: 'search', arguments: args }, +}) + +describe('toolCallInputIssues', () => { + it('names the fields a native query carries that the schema does not know', () => { + const issues = toolCallInputIssues( + searchCall({ + query: 'roadmap', + nativeQueries: [{ provider: 'jira', query: 'roadmap', limit: 5 }], + }) + ) + expect(issues?.issues).toContainEqual({ + code: 'unrecognized_keys', + path: 'nativeQueries.0', + keys: ['limit'], + }) + }) +}) diff --git a/apps/sim/lib/knowledge/mcp/tool-arguments.ts b/apps/sim/lib/knowledge/mcp/tool-arguments.ts index 0e0603ed0ae..384cafcf20a 100644 --- a/apps/sim/lib/knowledge/mcp/tool-arguments.ts +++ b/apps/sim/lib/knowledge/mcp/tool-arguments.ts @@ -87,9 +87,18 @@ export function repairToolCallMessage(message: Record) { return { message: { ...message, params: { ...call, arguments: args } }, repairs: [...repairs] } } +/** + * Unrecognized field names logged per schema issue, each cut short. The route's request body cap + * bounds how many issues one rejection can carry. + */ +const MAX_LOGGED_KEYS = 5 +const MAX_LOGGED_KEY_CHARACTERS = 64 + /** * Where a tool call's arguments fail its input schema, as field paths and issue codes without - * values. The SDK rejects such a call before the tool runs, so nothing else records it. + * values, plus the names of fields the schema does not know, so a repeated client mistake can be + * told apart from a new one. The SDK rejects such a call before the tool runs, so nothing else + * records it. */ export function toolCallInputIssues(message: Record) { const call = readToolCall(message) @@ -99,6 +108,14 @@ export function toolCallInputIssues(message: Record) { if (parsed.success) return undefined return { toolName: call.name, - issues: parsed.error.issues.map(({ code, path }) => ({ code, path: path.join('.') })), + issues: parsed.error.issues.map((issue) => ({ + code: issue.code, + path: issue.path.join('.'), + ...(issue.code === 'unrecognized_keys' && { + keys: issue.keys + .slice(0, MAX_LOGGED_KEYS) + .map((key) => key.slice(0, MAX_LOGGED_KEY_CHARACTERS)), + }), + })), } } diff --git a/apps/sim/lib/mothership/generated/docs-manifest.ts b/apps/sim/lib/mothership/generated/docs-manifest.ts index d193d1fab82..93922c126d2 100644 --- a/apps/sim/lib/mothership/generated/docs-manifest.ts +++ b/apps/sim/lib/mothership/generated/docs-manifest.ts @@ -254,6 +254,7 @@ export const DOCS_MANIFEST: readonly string[] = [ 'integrations/mintlify.mdx', 'integrations/mistral_parse.mdx', 'integrations/modal.mdx', + 'integrations/monaco.mdx', 'integrations/monday-service-account.mdx', 'integrations/monday.mdx', 'integrations/mongodb.mdx', diff --git a/apps/sim/lib/mothership/tools/client/computer-tool-result.test.ts b/apps/sim/lib/mothership/tools/client/computer-tool-result.test.ts index 4eb5a6c0d70..0e9fb9d563c 100644 --- a/apps/sim/lib/mothership/tools/client/computer-tool-result.test.ts +++ b/apps/sim/lib/mothership/tools/client/computer-tool-result.test.ts @@ -198,6 +198,81 @@ describe('native computer model projection', () => { expect(result).toMatchObject({ dispatched: true, sequence: { completedSteps: 1 } }) }) + it('folds empty layout groups so their controls fit and point at a shown parent', () => { + const state = snapshot() + state.nodes = [ + { elementId: 'window', role: 'AXWindow', label: 'Fixture', actions: [], windowId: '1' }, + ...Array.from({ length: 300 }, (_, index) => ({ + elementId: `wrap-${index}`, + parentId: index === 0 ? 'window' : `wrap-${index - 1}`, + role: 'AXGroup', + actions: [], + windowId: '1', + })), + ...Array.from({ length: 150 }, (_, index) => ({ + elementId: `button-${index}`, + parentId: 'wrap-299', + role: 'AXButton', + label: `Action ${index}`, + actions: ['AXPress'], + windowId: '1', + })), + ] + const result = computerToolResultForModel(state) + if (!('accessibilityTree' in result)) throw new Error('Missing projected tree') + const rows = result.accessibilityTree.split('\n') + expect(rows.some((row) => row.includes('AXGroup'))).toBe(false) + expect(rows.filter((row) => row.includes('AXButton'))).toHaveLength(150) + expect(rows.find((row) => row.startsWith('button-0 '))).toContain('parent=window') + expect(result.omittedNodeCount).toBe(0) + }) + + it('never names a node as its own parent through a wrapper cycle', () => { + const state = snapshot() + state.nodes = [ + { + elementId: 'button', + parentId: 'wrap', + role: 'AXButton', + label: 'Go', + actions: ['AXPress'], + }, + { elementId: 'wrap', parentId: 'button', role: 'AXGroup', actions: [] }, + ] + const result = computerToolResultForModel(state) + if (!('accessibilityTree' in result)) throw new Error('Missing projected tree') + expect(result.accessibilityTree).toContain('button AXButton') + expect(result.accessibilityTree).not.toContain('parent=button') + }) + + it('keeps an empty layout group that reports itself disabled', () => { + const state = snapshot() + state.nodes = [ + { elementId: 'window', role: 'AXWindow', label: 'Fixture', actions: [], windowId: '1' }, + { + elementId: 'pane', + parentId: 'window', + role: 'AXGroup', + enabled: false, + actions: [], + windowId: '1', + }, + { + elementId: 'save', + parentId: 'pane', + role: 'AXButton', + label: 'Save', + actions: ['AXPress'], + windowId: '1', + }, + ] + const result = computerToolResultForModel(state) + if (!('accessibilityTree' in result)) throw new Error('Missing projected tree') + const rows = result.accessibilityTree.split('\n') + expect(rows.find((row) => row.startsWith('pane '))).toContain('disabled') + expect(rows.find((row) => row.startsWith('save '))).toContain('parent=pane') + }) + it('does not split an emoji when bounding text for the model', () => { const state = snapshot() state.nodes[0] = { diff --git a/apps/sim/lib/mothership/tools/client/computer-tool-result.ts b/apps/sim/lib/mothership/tools/client/computer-tool-result.ts index 8fb6de95690..72db13ab9cf 100644 --- a/apps/sim/lib/mothership/tools/client/computer-tool-result.ts +++ b/apps/sim/lib/mothership/tools/client/computer-tool-result.ts @@ -24,13 +24,38 @@ function jsonBytes(value: unknown): number { return encoder.encode(JSON.stringify(value)).length } +type SnapshotNode = ComputerUseSnapshot['nodes'][number] + +/** Layout wrappers that carry no text, state (including disabled) or action; their children are shown under the nearest meaningful ancestor. */ +const WRAPPER_ROLES = new Set([ + 'AXGroup', + 'AXSplitGroup', + 'AXLayoutArea', + 'AXLayoutItem', + 'AXUnknown', +]) + +function isWrapper(node: SnapshotNode): boolean { + return ( + WRAPPER_ROLES.has(node.role) && + !node.label && + !node.value && + !node.placeholder && + !node.focused && + !node.editable && + node.enabled !== false && + node.actions.length === 0 + ) +} + function nodeRow( - node: ComputerUseSnapshot['nodes'][number], + node: SnapshotNode, + parentId: string | undefined, + selectedWindowId: string, selected?: ReadonlySet ): string { const fields = [node.elementId, node.role] - if (node.parentId && (!selected || selected.has(node.parentId))) - fields.push(`parent=${node.parentId}`) + if (parentId && (!selected || selected.has(parentId))) fields.push(`parent=${parentId}`) if (node.label) fields.push(`label=${JSON.stringify(truncateAtCodePoint(node.label, 512, ''))}`) if (node.value) fields.push(`value=${JSON.stringify(truncateAtCodePoint(node.value, 512, ''))}`) if (node.placeholder) @@ -39,14 +64,16 @@ function nodeRow( if (node.editable) fields.push('editable') if (node.enabled === false) fields.push('disabled') if (node.actions.length) fields.push(`actions=${JSON.stringify(node.actions.slice(0, 16))}`) - if (node.windowId) fields.push(`window=${node.windowId}`) + if (node.windowId && node.windowId !== selectedWindowId) fields.push(`window=${node.windowId}`) if ( node.x !== undefined && node.y !== undefined && node.width !== undefined && node.height !== undefined ) - fields.push(`screenRect=${JSON.stringify([node.x, node.y, node.width, node.height])}`) + fields.push( + `screenRect=${[node.x, node.y, node.width, node.height].map((value) => Math.round(value)).join(',')}` + ) return fields.join(' ') } @@ -70,8 +97,10 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) { : {}), windows, omittedWindowCount: allWindows.length - windows.length, + treeFormat: + 'Rows: elementId role [parent=id, omitted when the parent is not shown] [label=] [value=] [placeholder=] (JSON strings) [focused] [editable] [disabled] [actions=JSON array of exact names] [window=id, omitted for windowId] [screenRect=x,y,w,h in global points]. Empty layout groups are folded into their parent.', accessibilityTree: '', - omittedNodeCount: nodes.length, + omittedNodeCount: nodes.filter((node) => !isWrapper(node)).length, nodeTextLimit: 512, ...(image ? { @@ -87,8 +116,30 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) { model.omittedWindowCount += 1 } const byId = new Map(nodes.map((node) => [node.elementId, node])) - const rows = new Map(nodes.map((node) => [node.elementId, nodeRow(node)])) - const priority = (node: ComputerUseSnapshot['nodes'][number]) => { + const shown = nodes.filter((node) => !isWrapper(node)) + /** Each shown node's nearest shown ancestor, skipping wrappers (and stopping on a parent cycle). */ + const shownParent = new Map() + for (const node of shown) { + const seen = new Set([node.elementId]) + let parent = node.parentId ? byId.get(node.parentId) : undefined + while (parent && isWrapper(parent) && !seen.has(parent.elementId)) { + seen.add(parent.elementId) + parent = parent.parentId ? byId.get(parent.parentId) : undefined + } + shownParent.set( + node.elementId, + parent && !isWrapper(parent) && parent.elementId !== node.elementId + ? parent.elementId + : undefined + ) + } + const rows = new Map( + shown.map((node) => [ + node.elementId, + nodeRow(node, shownParent.get(node.elementId), state.windowId), + ]) + ) + const priority = (node: SnapshotNode) => { const visible = selectedWindow && node.windowId === selectedWindow.windowId && @@ -127,20 +178,21 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) { let bytes = jsonBytes(model) + envelopeBytes const add = (ids: string[]) => { const additional = ids.filter((id) => !selected.has(id)) - const cost = additional.reduce((total, id) => total + jsonBytes(rows.get(id)) - 2 + 2, 0) + /** A row's JSON length: its two quotes come off, and the escaped joining newline (`\\n`) adds two bytes back. */ + const cost = additional.reduce((total, id) => total + jsonBytes(rows.get(id)), 0) if (bytes + cost > MAX_STATE_BYTES) return false for (const id of additional) selected.add(id) bytes += cost return true } - for (const node of [...nodes].sort((left, right) => priority(right) - priority(left))) { + for (const node of [...shown].sort((left, right) => priority(right) - priority(left))) { const ancestry = [node.elementId] const seen = new Set(ancestry) - let parent = node.parentId - while (parent && byId.has(parent) && !seen.has(parent)) { + let parent = shownParent.get(node.elementId) + while (parent && !seen.has(parent)) { ancestry.push(parent) seen.add(parent) - parent = byId.get(parent)?.parentId + parent = shownParent.get(parent) } /** Keep the editor itself even if an unusually deep ancestry cannot fit. */ if (!add(ancestry.reverse())) add([node.elementId]) @@ -155,14 +207,14 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) { ) || allWindows.some((window) => window.title.length > 256) return { ...model, - accessibilityTree: nodes + accessibilityTree: shown .filter((node) => selected.has(node.elementId)) - .map((node) => nodeRow(node, selected)) + .map((node) => nodeRow(node, shownParent.get(node.elementId), state.windowId, selected)) .join('\n'), - omittedNodeCount: nodes.length - selected.size, + omittedNodeCount: shown.length - selected.size, truncated: state.truncated || - selected.size < nodes.length || + selected.size < shown.length || windows.length < allWindows.length || clipped || screenshotOmitted, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index efa2a7160cf..ed881ddac3d 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -124,6 +124,7 @@ import { MicrosoftSqlIcon, MicrosoftTeamsIcon, MicrosoftWordIcon, + MonacoIcon, MondayIcon, MySQLIcon, NetSuiteIcon, @@ -3866,6 +3867,23 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'harmonic', }, + monaco: { + name: 'Monaco', + icon: MonacoIcon, + services: { + monaco: { + name: 'Monaco', + description: 'Manage Monaco contacts, accounts, opportunities, tasks, and outreach.', + providerId: 'monaco', + serviceAccountProviderId: 'monaco-service-account', + icon: MonacoIcon, + baseProviderIcon: MonacoIcon, + scopes: [], + authType: 'service_account', + }, + }, + defaultService: 'monaco', + }, linkedin: { name: 'LinkedIn', icon: LinkedInIcon, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 7daa861af08..7b407976c52 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -141,6 +141,7 @@ export type OAuthProvider = | 'vanta' | 'hubspot' | 'harmonic' + | 'monaco' | 'coda' | 'gauge' | 'higgsfield' @@ -335,6 +336,7 @@ export type OAuthService = | 'vanta' | 'hubspot' | 'harmonic' + | 'monaco' | 'coda' | 'gauge' | 'higgsfield' diff --git a/apps/sim/lib/sim-search/live/providers.test.ts b/apps/sim/lib/sim-search/live/providers.test.ts index e6299d5aa30..50c69722f2a 100644 --- a/apps/sim/lib/sim-search/live/providers.test.ts +++ b/apps/sim/lib/sim-search/live/providers.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { readGitHub, searchGitHub } from '@/lib/sim-search/live/github' import { searchCalendar, searchDrive } from '@/lib/sim-search/live/google' +import { defaultLiveSearchPolicy } from '@/lib/sim-search/live/policy-schema' import { searchSlack } from '@/lib/sim-search/live/slack' import type { NativeClient } from '@/lib/sim-search/live/types' @@ -151,7 +152,7 @@ describe('native search endpoints', () => { query: 'launch', channel_types: ['public_channel', 'im'], include_archived_channels: true, - content_types: ['messages'], + content_types: ['messages', 'channels'], include_context_messages: true, limit: 20, term_clauses: ['launch OR release'], @@ -384,3 +385,151 @@ describe('Slack RTS pages', () => { ).rejects.toMatchObject({ status: 'rate_limited' }) }) }) + +describe('Slack channels', () => { + const scopes = ['search:read.public', 'search:read.private'] + const channel = (id: string, name: string, archived = false) => ({ + name, + is_archived: archived, + permalink: `https://team.slack.com/archives/${id}`, + }) + const rts = (channels: unknown[], messages: unknown[] = []) => ({ + ok: true, + results: { messages, channels }, + }) + + it('names the channels a query matches that the source settings let it search', async () => { + const api = client() + api.json.mockResolvedValue( + rts([ + channel('C1', 'announcements'), + channel('G2', 'announce-leads'), + channel('C3', 'announce-old', true), + ]) + ) + const result = await searchSlack(api, { + ...input, + scopes, + policy: { ...defaultLiveSearchPolicy('slack'), excluded: ['G2'], includeArchived: false }, + native: { provider: 'slack', query: 'announcements' }, + }) + expect(result.message).toContain('Channels matching the query: #announcements (in:<#C1>).') + expect(result.message).not.toContain('announce-leads') + expect(result.message).not.toContain('announce-old') + }) + + it('says a filtered channel exists when none of its messages match', async () => { + const api = client() + api.json + .mockResolvedValueOnce(rts([])) + .mockResolvedValueOnce(rts([channel('C9', 'launch-team'), channel('C8', 'launch-team-ops')])) + const result = await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers: 'in:#Launch-Team' }, + }) + expect(result.documents).toEqual([]) + expect(result.message).toContain( + '#launch-team (in:<#C9>) exists; none of its messages matched this query and date window.' + ) + }) + + it('says no channel has the filtered name and names the similar ones', async () => { + const api = client() + api.json + .mockResolvedValueOnce(rts([])) + .mockResolvedValueOnce(rts([channel('C7', 'company-announcements')])) + const result = await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers: 'in:#announcements' }, + }) + expect(result.message).toContain('No channel named #announcements can be searched here.') + expect(result.message).toContain( + 'Channels with similar names: #company-announcements (in:<#C7>).' + ) + }) + + it('does not call a channel missing while the lookup has more channels to return', async () => { + const api = client() + api.json.mockResolvedValueOnce(rts([])).mockResolvedValueOnce({ + ...rts([channel('C7', 'company-announcements')]), + response_metadata: { next_cursor: 'more' }, + }) + const result = await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers: 'in:#announcements' }, + }) + expect(api.json.mock.calls[1]?.[1]?.body).toMatchObject({ limit: 20 }) + expect(result.message).not.toContain('No channel named') + expect(result.message).toContain('Channels with similar names: #company-announcements') + }) + + it('says nothing about the filtered channel while Slack has another page of results', async () => { + const api = client() + api.json.mockResolvedValueOnce({ ...rts([]), response_metadata: { next_cursor: 'page-2' } }) + const result = await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers: 'in:#announcements' }, + }) + expect(result.message).not.toContain('#announcements') + }) + + it('looks up no channel when the query is already scoped to a channel ID', async () => { + const api = client() + api.json.mockResolvedValue(rts([])) + const result = await searchSlack(api, { + ...input, + scopes, + native: { provider: 'slack', query: 'launch in:<#C1>', modifiers: 'in:#announcements' }, + }) + expect(api.json).toHaveBeenCalledTimes(1) + expect(result.message).not.toContain('#announcements') + }) + + it.each([ + ['in:#launch-team.', 'launch-team'], + ['in:#équipe_ventes,', 'équipe_ventes'], + ['in:#हिंदी-टीम', 'हिंदी-टीम'], + ])('looks up the whole channel name in %s', async (modifiers, name) => { + const api = client() + api.json.mockResolvedValue(rts([])) + await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers }, + }) + expect(api.json.mock.calls[1]?.[1]?.body).toMatchObject({ + query: name, + content_types: ['channels'], + }) + }) + + it('keeps the empty search when the channel lookup fails', async () => { + const api = client() + api.json + .mockResolvedValueOnce(rts([])) + .mockResolvedValueOnce({ ok: false, error: 'ratelimited' }) + const result = await searchSlack(api, { + ...input, + query: '', + scopes, + filters: { startDate: '2026-10-30T00:00:00Z' }, + native: { provider: 'slack', query: '', modifiers: 'in:#announcements' }, + }) + expect(result.documents).toEqual([]) + expect(result.message).not.toContain('#announcements') + }) +}) diff --git a/apps/sim/lib/sim-search/live/slack.ts b/apps/sim/lib/sim-search/live/slack.ts index 39701e4c970..d01a35f5b8b 100644 --- a/apps/sim/lib/sim-search/live/slack.ts +++ b/apps/sim/lib/sim-search/live/slack.ts @@ -1,6 +1,7 @@ import { dateSortDirection, nativeDateBounds, nativeText } from '@/lib/sim-search/live/dates' import { array, NativeSearchError, object, string } from '@/lib/sim-search/live/http' import { joinMessages } from '@/lib/sim-search/live/pages' +import { permitsResources } from '@/lib/sim-search/live/policy' import { slackConversationName, slackConversationUrl, @@ -36,6 +37,85 @@ function slackResult(value: unknown) { return data } +/** Channels a search names in its status, so the client can filter on one with `in:<#ID>`. */ +const MAX_LISTED_CHANNELS = 5 + +interface SlackChannel { + id: string + name: string + archived: boolean +} + +/** + * Channels from an RTS response that the account's source settings let it search. The ID comes + * from the permalink, as RTS channel results carry no ID field. + */ +function searchableChannels(data: Record, input: NativeSearchInput) { + return array(object(data.results).channels).flatMap((channel): SlackChannel[] => { + const id = string(channel.permalink).match(/\/archives\/([CG][A-Z0-9]+)$/)?.[1] + const name = string(channel.name) + const archived = channel.is_archived === true + if (!id || !name) return [] + if (input.policy && !permitsResources(input.policy, [id])) return [] + if (archived && input.policy?.includeArchived === false) return [] + return [{ id, name, archived }] + }) +} + +const channelLabel = ({ id, name, archived }: SlackChannel) => + `#${name} (in:<#${id}>${archived ? ', archived' : ''})` + +/** + * The channel name an `in:#name` filter in the query or modifiers names, without a trailing period. + * A query that also carries an `in:<#ID>` scope gets no lookup: a source limited to selected + * channels adds one to each search it runs per channel and drops the notes of the empty ones. + */ +function filteredChannelName(input: NativeSearchInput): string | undefined { + const text = [nativeText(input), input.native?.modifiers].join(' ') + if (text.includes('in:<#')) return undefined + return text.match(/(?:^|\s)in:#([\p{L}\p{M}\p{N}_.-]*[\p{L}\p{M}\p{N}_-])/iu)?.[1]?.toLowerCase() +} + +/** + * Whether the channel a query filtered to exists, said when the query found nothing, since RTS + * returns the same empty page for a quiet channel and a name that matches no channel. A lookup + * that fails says nothing rather than failing the search. + */ +async function channelFilterNote( + client: NativeClient, + input: NativeSearchInput, + name: string, + channelTypes: string[] +): Promise { + const data = await client + .json('/api/assistant.search.context', { + body: { + query: name, + channel_types: channelTypes, + include_archived_channels: input.policy?.includeArchived ?? true, + content_types: ['channels'], + limit: SLACK_PAGE_SIZE, + }, + }) + .then(slackResult) + .catch((error: unknown) => { + if (input.signal?.aborted) throw error + return undefined + }) + if (!data) return undefined + const channels = searchableChannels(data, input) + const named = channels.find((channel) => channel.name.toLowerCase() === name) + if (named) + return `${channelLabel(named)} exists; none of its messages matched this query and date window.` + /** A lookup with another page has not ruled the name out, so it only names what it found. */ + const complete = !string(object(data.response_metadata).next_cursor) + const similar = channels.slice(0, MAX_LISTED_CHANNELS).map(channelLabel).join(', ') + return joinMessages([ + complete ? `No channel named #${name} can be searched here.` : '', + similar ? `Channels with similar names: ${similar}.` : '', + ]) +} + /** Results one Real-time Search call returns at most. */ const SLACK_PAGE_SIZE = 20 /** @@ -117,6 +197,8 @@ async function searchSlackPage( .filter(Boolean) .join(' ') const modifiers = [input.native?.modifiers, dateModifiers].filter(Boolean).join(' ') + /** Channels match on name, topic and purpose, so only a query with text lists them, once. */ + const listChannels = Boolean(nativeText(input)) && !cursor const data = slackResult( await client.json('/api/assistant.search.context', { body: { @@ -125,9 +207,11 @@ async function searchSlackPage( .join(' '), channel_types: channels, include_archived_channels: input.policy?.includeArchived ?? true, - content_types: input.scopes.includes('search:read.files') - ? ['messages', 'files'] - : ['messages'], + content_types: [ + 'messages', + ...(input.scopes.includes('search:read.files') ? ['files'] : []), + ...(listChannels ? ['channels'] : []), + ], include_context_messages: true, limit: Math.min(limit, SLACK_PAGE_SIZE), ...(dates.start ? { after: Math.floor(Date.parse(dates.start) / 1000) - 1 } : {}), @@ -216,10 +300,19 @@ async function searchSlackPage( : {}), }) } + const listed = listChannels ? searchableChannels(data, input).slice(0, MAX_LISTED_CHANNELS) : [] + const nextCursor = string(object(data.response_metadata).next_cursor) || undefined + /** Only a search with no result on any page can say what its channel filter matched. */ + const filtered = + !documents.length && !cursor && !nextCursor ? filteredChannelName(input) : undefined return { documents, - nextCursor: string(object(data.response_metadata).next_cursor) || undefined, - message: `Slack RTS searches ${channels.join(', ')} with your user token. Semantic matching depends on your Slack plan; date ordering uses keyword retrieval. Message timestamps are posting dates; edits may not be reflected in date filtering.`, + nextCursor, + message: joinMessages([ + `Slack RTS searches ${channels.join(', ')} with your user token. Semantic matching depends on your Slack plan; date ordering uses keyword retrieval. Message timestamps are posting dates; edits may not be reflected in date filtering.`, + listed.length ? `Channels matching the query: ${listed.map(channelLabel).join(', ')}.` : '', + filtered ? await channelFilterNote(client, input, filtered, channels) : '', + ]), } } diff --git a/apps/sim/tools/generated/tool-ids.ts b/apps/sim/tools/generated/tool-ids.ts index bd7f48bdeb7..1aec7380f7f 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","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","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_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_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","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_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","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_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","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_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","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","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","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_aggregated_post_metrics","buffer_get_channel","buffer_get_channels","buffer_get_daily_posting_limits","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_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","checkr_add_report_tag","checkr_apply_report_review_action","checkr_cancel_adverse_action","checkr_cancel_continuous_check","checkr_cancel_invitation","checkr_cancel_subscription","checkr_complete_report","checkr_create_adverse_action","checkr_create_candidate","checkr_create_continuous_check","checkr_create_geo","checkr_create_invitation","checkr_create_report","checkr_create_subscription","checkr_delete_candidate_pii","checkr_delete_geo","checkr_get_account","checkr_get_adverse_action","checkr_get_candidate","checkr_get_continuous_check","checkr_get_document","checkr_get_geo","checkr_get_invitation","checkr_get_node","checkr_get_package","checkr_get_program","checkr_get_report","checkr_get_report_eta","checkr_get_report_progressive_status","checkr_get_report_tags","checkr_get_screening","checkr_get_subscription","checkr_get_verification","checkr_list_adverse_actions","checkr_list_adverse_items","checkr_list_assessments","checkr_list_candidate_documents","checkr_list_candidates","checkr_list_continuous_checks","checkr_list_counties","checkr_list_geos","checkr_list_invitations","checkr_list_nodes","checkr_list_packages","checkr_list_programs","checkr_list_report_addresses","checkr_list_subscriptions","checkr_list_users","checkr_list_verifications","checkr_remove_report_tag","checkr_set_report_tags","checkr_update_candidate","checkr_update_continuous_check","checkr_update_geo","checkr_update_report","checkr_update_subscription","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","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_access_application","cloudflare_create_access_group","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_tunnel","cloudflare_create_worker_route","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_group","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_r2_bucket_cors","cloudflare_delete_ruleset","cloudflare_delete_ruleset_rule","cloudflare_delete_tunnel","cloudflare_delete_worker_route","cloudflare_delete_worker_script","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_access_group","cloudflare_get_access_policy","cloudflare_get_access_service_token","cloudflare_get_dns_record","cloudflare_get_r2_bucket","cloudflare_get_r2_bucket_cors","cloudflare_get_r2_bucket_lifecycle","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_tunnel_token","cloudflare_get_worker_route","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_refresh_access_service_token","cloudflare_revoke_access_service_token","cloudflare_rotate_access_service_token","cloudflare_update_access_application","cloudflare_update_access_group","cloudflare_update_access_policy","cloudflare_update_access_service_token","cloudflare_update_dns_record","cloudflare_update_r2_bucket_cors","cloudflare_update_r2_bucket_lifecycle","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset","cloudflare_update_ruleset_rule","cloudflare_update_tunnel","cloudflare_update_tunnel_configuration","cloudflare_update_worker_route","cloudflare_update_zone","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","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","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","coda_add_custom_domain","coda_add_permission","coda_change_user_role","coda_create_doc","coda_create_folder","coda_create_page","coda_delete_custom_domain","coda_delete_doc","coda_delete_folder","coda_delete_page","coda_delete_page_content","coda_delete_permission","coda_delete_row","coda_delete_rows","coda_export_page","coda_get_acl_settings","coda_get_analytics_last_updated","coda_get_column","coda_get_control","coda_get_custom_domain_provider","coda_get_doc","coda_get_doc_analytics_summary","coda_get_folder","coda_get_formula","coda_get_mutation_status","coda_get_page","coda_get_page_content","coda_get_page_export_status","coda_get_row","coda_get_sharing_metadata","coda_get_table","coda_list_categories","coda_list_columns","coda_list_controls","coda_list_custom_domains","coda_list_doc_analytics","coda_list_docs","coda_list_folder_children","coda_list_folders","coda_list_formulas","coda_list_page_analytics","coda_list_pages","coda_list_permissions","coda_list_rows","coda_list_tables","coda_list_workspace_members","coda_list_workspace_roles","coda_publish_doc","coda_push_button","coda_resolve_browser_link","coda_search_principals","coda_trigger_automation","coda_unpublish_doc","coda_update_acl_settings","coda_update_doc","coda_update_folder","coda_update_page","coda_update_row","coda_upsert_rows","coda_whoami","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_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","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_all_runs","databricks_cancel_run","databricks_cancel_statement","databricks_create_cluster","databricks_create_job","databricks_create_warehouse","databricks_delete_cluster","databricks_delete_job","databricks_delete_run","databricks_delete_warehouse","databricks_edit_cluster","databricks_edit_warehouse","databricks_execute_sql","databricks_genie_agent_ask","databricks_genie_agent_cancel","databricks_genie_agent_list_items","databricks_genie_ask","databricks_genie_create_space","databricks_genie_delete_conversation","databricks_genie_delete_message","databricks_genie_delete_space","databricks_genie_download_visualization","databricks_genie_execute_query","databricks_genie_get_message","databricks_genie_get_query_result","databricks_genie_get_space","databricks_genie_list_conversations","databricks_genie_list_messages","databricks_genie_list_spaces","databricks_genie_send_feedback","databricks_genie_update_space","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_get_statement_chunk","databricks_get_warehouse","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_repair_run","databricks_reset_job","databricks_resize_cluster","databricks_restart_cluster","databricks_run_job","databricks_start_cluster","databricks_start_warehouse","databricks_stop_warehouse","databricks_submit_run","databricks_terminate_cluster","databricks_update_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","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_download_v2","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_get_qr_code_v2","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","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","ec2_allocate_address","ec2_associate_address","ec2_authorize_security_group_ingress","ec2_create_snapshot","ec2_create_tags","ec2_delete_snapshot","ec2_delete_tags","ec2_delete_volume","ec2_describe_addresses","ec2_describe_availability_zones","ec2_describe_images","ec2_describe_instance_status","ec2_describe_instances","ec2_describe_internet_gateways","ec2_describe_key_pairs","ec2_describe_nat_gateways","ec2_describe_network_acls","ec2_describe_network_interfaces","ec2_describe_regions","ec2_describe_route_tables","ec2_describe_security_groups","ec2_describe_snapshots","ec2_describe_subnets","ec2_describe_volumes","ec2_describe_vpc_endpoints","ec2_describe_vpc_peering_connections","ec2_describe_vpcs","ec2_disassociate_address","ec2_reboot_instances","ec2_release_address","ec2_revoke_security_group_ingress","ec2_start_instances","ec2_stop_instances","ec2_terminate_instances","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","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","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","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_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","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_enrich","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","fivetran_cancel_transformation","fivetran_create_connect_card","fivetran_create_connection","fivetran_create_destination","fivetran_create_group","fivetran_delete_connection","fivetran_get_account_info","fivetran_get_connection","fivetran_get_connection_schema_config","fivetran_get_connection_warnings","fivetran_get_connector_type","fivetran_get_destination","fivetran_get_group","fivetran_get_sync_history","fivetran_get_transformation","fivetran_list_connections","fivetran_list_connector_types","fivetran_list_destinations","fivetran_list_groups","fivetran_list_transformations","fivetran_list_users","fivetran_reload_connection_schema","fivetran_resync_connection","fivetran_resync_tables","fivetran_run_connection_setup_tests","fivetran_run_destination_setup_tests","fivetran_run_transformation","fivetran_sync_connection","fivetran_update_connection","fivetran_update_connection_schema","fivetran_update_connection_table","fivetran_update_schema_config","flint_create_task","flint_generate_pages","flint_get_task","flow_add_link","flow_create_branch","flow_create_comment","flow_create_entities","flow_create_entity","flow_delete_comment","flow_delete_entity","flow_filter_entities","flow_get_comment","flow_get_data_model","flow_get_entities","flow_get_entity_history","flow_get_project","flow_list_branches","flow_list_comments","flow_list_entities","flow_list_projects","flow_remove_link","flow_restore_entity","flow_search_entities","flow_set_value","flow_set_values","flow_update_comment","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","gauge_add_tags_to_prompts","gauge_bulk_update_prompts","gauge_create_prompts","gauge_create_tag","gauge_create_topic","gauge_delete_tag","gauge_get_answer","gauge_get_category","gauge_get_current_credential","gauge_get_organization","gauge_get_prompt","gauge_get_query_metadata","gauge_get_tag","gauge_get_topic","gauge_list_answers","gauge_list_brands","gauge_list_categories","gauge_list_citations","gauge_list_organizations","gauge_list_prompts","gauge_list_tags","gauge_list_topics","gauge_query","gauge_remove_tags_from_prompts","gauge_rename_topic","gauge_update_prompt","gauge_update_tag","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_respond","google_calendar_respond_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_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","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_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","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_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_apply_candidate_tag_v2","greenhouse_convert_prospect_v2","greenhouse_create_application_v2","greenhouse_create_attachment_v2","greenhouse_create_candidate_tag_v2","greenhouse_create_candidate_v2","greenhouse_create_note_v2","greenhouse_delete_application_v2","greenhouse_delete_attachment_v2","greenhouse_delete_candidate_v2","greenhouse_get_application","greenhouse_get_application_v2","greenhouse_get_candidate","greenhouse_get_candidate_v2","greenhouse_get_job","greenhouse_get_job_v2","greenhouse_get_user","greenhouse_get_user_v2","greenhouse_hire_application_v2","greenhouse_list_application_stages_v2","greenhouse_list_applications","greenhouse_list_applications_v2","greenhouse_list_applied_candidate_tags_v2","greenhouse_list_attachments_v2","greenhouse_list_candidate_educations_v2","greenhouse_list_candidate_employments_v2","greenhouse_list_candidate_tags_v2","greenhouse_list_candidates","greenhouse_list_candidates_v2","greenhouse_list_close_reasons_v2","greenhouse_list_custom_field_options_v2","greenhouse_list_custom_fields_v2","greenhouse_list_departments","greenhouse_list_departments_v2","greenhouse_list_email_templates_v2","greenhouse_list_interviews_v2","greenhouse_list_job_hiring_managers_v2","greenhouse_list_job_interview_stages_v2","greenhouse_list_job_owners_v2","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_jobs_v2","greenhouse_list_notes_v2","greenhouse_list_offers_v2","greenhouse_list_offices","greenhouse_list_offices_v2","greenhouse_list_openings_v2","greenhouse_list_prospect_pool_stages_v2","greenhouse_list_prospect_pools_v2","greenhouse_list_referrers_v2","greenhouse_list_rejection_reasons_v2","greenhouse_list_scorecards_v2","greenhouse_list_sources_v2","greenhouse_list_users","greenhouse_list_users_v2","greenhouse_move_application_v2","greenhouse_reject_application_v2","greenhouse_remove_candidate_tag_v2","greenhouse_unreject_application_v2","greenhouse_update_application_v2","greenhouse_update_candidate_v2","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","helpscout_assign_conversation","helpscout_create_conversation","helpscout_create_customer","helpscout_create_customer_thread","helpscout_create_note","helpscout_create_reply","helpscout_delete_conversation","helpscout_get_conversation","helpscout_get_current_user","helpscout_get_customer","helpscout_get_organization","helpscout_get_saved_reply","helpscout_list_conversations","helpscout_list_custom_fields","helpscout_list_customer_properties","helpscout_list_customers","helpscout_list_folders","helpscout_list_mailboxes","helpscout_list_organizations","helpscout_list_saved_replies","helpscout_list_tags","helpscout_list_teams","helpscout_list_threads","helpscout_list_users","helpscout_list_workflows","helpscout_move_conversation","helpscout_run_workflow","helpscout_set_conversation_tags","helpscout_snooze_conversation","helpscout_unassign_conversation","helpscout_unsnooze_conversation","helpscout_update_conversation_customer","helpscout_update_conversation_status","helpscout_update_conversation_subject","helpscout_update_custom_fields","helpscout_update_customer","helpscout_update_customer_properties","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","higgsfield_cancel_request","higgsfield_create_character","higgsfield_delete_character","higgsfield_delete_request","higgsfield_generate","higgsfield_generate_soul_image","higgsfield_get_character","higgsfield_get_request_status","higgsfield_list_characters","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_policy","iam_get_role","iam_get_user","iam_list_access_keys","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","iam_update_access_key","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_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","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_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","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_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","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","intune_get_compliance_policy","intune_get_detected_app","intune_get_device","intune_get_device_configuration","intune_list_app_devices","intune_list_compliance_policies","intune_list_compliance_policy_device_statuses","intune_list_detected_apps","intune_list_device_configurations","intune_list_devices","intune_reboot_device","intune_remote_lock_device","intune_retire_device","intune_sync_device","jama_create_comment","jama_create_item","jama_create_relationship","jama_delete_item","jama_delete_relationship","jama_get_baseline","jama_get_current_user","jama_get_filter","jama_get_item","jama_get_item_type","jama_get_item_version","jama_get_project","jama_get_relationship","jama_get_test_cycle","jama_get_test_plan","jama_get_test_run","jama_list_baseline_items","jama_list_baselines","jama_list_downstream_items","jama_list_filter_results","jama_list_filters","jama_list_item_children","jama_list_item_comments","jama_list_item_types","jama_list_item_versions","jama_list_items","jama_list_pick_list_options","jama_list_pick_lists","jama_list_projects","jama_list_relationship_types","jama_list_relationships","jama_list_releases","jama_list_tags","jama_list_test_cycles","jama_list_test_plans","jama_list_test_runs","jama_list_upstream_items","jama_search_items","jama_update_item","jama_update_test_run","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_create_sprint","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","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","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_get_content_v2","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","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","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","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","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","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_user_memberships","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","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_download_file_v2","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_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_calculate","microsoft_excel_clear_range","microsoft_excel_close_session","microsoft_excel_create_session","microsoft_excel_create_table","microsoft_excel_delete_table","microsoft_excel_delete_table_row","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_get_table","microsoft_excel_get_table_row","microsoft_excel_get_worksheet","microsoft_excel_list_table_rows","microsoft_excel_list_tables","microsoft_excel_list_worksheets","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_refresh_session","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_update_table","microsoft_excel_update_table_row","microsoft_excel_update_worksheet","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","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","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","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_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","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","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_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","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","oracle_fusion_financials_apply_receivables_receipt","oracle_fusion_financials_approve_receivables_invoice","oracle_fusion_financials_create_expense_line","oracle_fusion_financials_create_expense_report","oracle_fusion_financials_create_receivables_credit_memo","oracle_fusion_financials_create_receivables_invoice","oracle_fusion_financials_create_receivables_receipt","oracle_fusion_financials_get_expense_line","oracle_fusion_financials_get_expense_report","oracle_fusion_financials_get_gl_journal_batch","oracle_fusion_financials_get_gl_ledger","oracle_fusion_financials_get_payables_invoice","oracle_fusion_financials_get_payables_invoice_line","oracle_fusion_financials_get_payables_payment","oracle_fusion_financials_get_receivables_credit_memo","oracle_fusion_financials_get_receivables_invoice","oracle_fusion_financials_get_receivables_invoice_installment","oracle_fusion_financials_get_receivables_invoice_line","oracle_fusion_financials_get_receivables_receipt","oracle_fusion_financials_list_expense_lines","oracle_fusion_financials_list_expense_reports","oracle_fusion_financials_list_gl_balances","oracle_fusion_financials_list_gl_journal_batches","oracle_fusion_financials_list_gl_ledgers","oracle_fusion_financials_list_payables_invoice_lines","oracle_fusion_financials_list_payables_invoices","oracle_fusion_financials_list_payables_payments","oracle_fusion_financials_list_receivables_credit_memos","oracle_fusion_financials_list_receivables_invoice_installments","oracle_fusion_financials_list_receivables_invoice_lines","oracle_fusion_financials_list_receivables_invoices","oracle_fusion_financials_list_receivables_receipts","oracle_fusion_financials_submit_expense_report","oracle_fusion_financials_update_expense_line","oracle_fusion_financials_update_expense_report","oracle_fusion_financials_update_receivables_invoice","oracledb_delete","oracledb_execute","oracledb_insert","oracledb_introspect","oracledb_query","oracledb_update","otter_create_conversation","otter_get_conversation","otter_get_conversation_audio","otter_get_workspace","otter_list_channel_members","otter_list_channels","otter_list_conversations","otter_list_workspace_conversations","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","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","plane_archive_project","plane_archive_work_item","plane_confirm_attachment_upload","plane_create_attachment_upload","plane_create_comment","plane_create_cycle","plane_create_label","plane_create_link","plane_create_module","plane_create_project","plane_create_project_page","plane_create_state","plane_create_webhook","plane_create_work_item","plane_delete_attachment","plane_delete_comment","plane_delete_cycle","plane_delete_label","plane_delete_link","plane_delete_module","plane_delete_project","plane_delete_project_page","plane_delete_state","plane_delete_webhook","plane_delete_work_item","plane_get_attachment","plane_get_comment","plane_get_current_user","plane_get_cycle","plane_get_label","plane_get_link","plane_get_module","plane_get_project","plane_get_project_page","plane_get_project_summary","plane_get_state","plane_get_webhook","plane_get_work_item","plane_get_work_item_by_identifier","plane_list_attachments","plane_list_comments","plane_list_cycles","plane_list_labels","plane_list_links","plane_list_modules","plane_list_project_pages","plane_list_projects","plane_list_states","plane_list_webhooks","plane_list_work_items","plane_list_workspace_work_items","plane_manage_cycle_work_items","plane_manage_module_work_items","plane_regenerate_webhook_secret","plane_transfer_cycle_work_items","plane_unarchive_project","plane_unarchive_work_item","plane_update_comment","plane_update_cycle","plane_update_label","plane_update_link","plane_update_module","plane_update_project","plane_update_project_page","plane_update_state","plane_update_webhook","plane_update_work_item","planetscale_close_deploy_request","planetscale_create_backup","planetscale_create_branch","planetscale_create_deploy_request","planetscale_delete_branch","planetscale_get_backup","planetscale_get_branch","planetscale_get_database","planetscale_get_deploy_request","planetscale_list_backups","planetscale_list_branches","planetscale_list_databases","planetscale_list_deploy_requests","planetscale_queue_deploy_request","planetscale_review_deploy_request","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","powerbi_execute_query","powerbi_get_dataset","powerbi_get_refresh_history","powerbi_get_report","powerbi_list_datasets","powerbi_list_reports","powerbi_list_workspaces","powerbi_refresh_dataset","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","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","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","ramp_create_memo","ramp_get_balance","ramp_get_bill","ramp_get_business","ramp_get_memo","ramp_get_physical_card","ramp_get_reimbursement","ramp_get_transaction","ramp_get_user","ramp_get_vendor","ramp_get_virtual_card","ramp_list_bills","ramp_list_physical_cards","ramp_list_reimbursements","ramp_list_transactions","ramp_list_users","ramp_list_vendors","ramp_list_virtual_cards","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","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","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","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","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_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","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_download_v2","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_create_product_variants","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_fulfillment","shopify_get_fulfillment_order","shopify_get_inventory_level","shopify_get_job","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_fulfillment_orders","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_fulfillment_tracking","shopify_update_order","shopify_update_product","shopify_update_product_variants","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_bookmark","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_close_conversation","slack_create_channel_canvas","slack_create_conversation","slack_create_user_group","slack_delete_canvas","slack_delete_file","slack_delete_message","slack_delete_scheduled_message","slack_disable_user_group","slack_download","slack_edit_bookmark","slack_edit_canvas","slack_enable_user_group","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_dnd_info","slack_get_file_info","slack_get_message","slack_get_permalink","slack_get_reactions","slack_get_team_dnd_info","slack_get_team_info","slack_get_team_profile","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_get_user_profile","slack_invite_to_conversation","slack_join_conversation","slack_kick_conversation","slack_leave_conversation","slack_list_bookmarks","slack_list_canvases","slack_list_channels","slack_list_emoji","slack_list_files","slack_list_members","slack_list_pins","slack_list_reactions","slack_list_scheduled_messages","slack_list_user_conversations","slack_list_user_group_members","slack_list_user_groups","slack_list_users","slack_lists_access_delete","slack_lists_access_set","slack_lists_create","slack_lists_download_get","slack_lists_download_start","slack_lists_items_create","slack_lists_items_delete","slack_lists_items_delete_multiple","slack_lists_items_info","slack_lists_items_list","slack_lists_items_update","slack_lists_update","slack_lookup_canvas_sections","slack_lookup_user_by_email","slack_mark_conversation_read","slack_message","slack_message_reader","slack_open_conversation","slack_open_view","slack_pin_message","slack_publish_view","slack_push_view","slack_remove_bookmark","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_revoke_canvas_access","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_set_user_presence","slack_share_canvas","slack_unarchive_conversation","slack_unfurl_links","slack_unpin_message","slack_update_message","slack_update_user_group","slack_update_user_group_members","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_cortex_analyst_ask","snowflake_cortex_analyst_feedback","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","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_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","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_download_file_v2","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","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","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_approve_user","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_device_attribute","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_attributes","tailscale_get_device_routes","tailscale_get_dns_configuration","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_get_split_dns","tailscale_get_tailnet_settings","tailscale_get_user","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_restore_user","tailscale_set_acl","tailscale_set_device_attribute","tailscale_set_device_name","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_configuration","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_set_split_dns","tailscale_set_user_role","tailscale_suspend_user","tailscale_update_device_key","tailscale_update_split_dns","tailscale_update_tailnet_settings","tailscale_validate_acl","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","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","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_higgsfield","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","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","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","youcom_answer","youcom_finance_research","youcom_get_account_balance","youcom_get_contents","youcom_get_research_task","youcom_research","youcom_search","youcom_search_images","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_create_account","zoho_desk_create_contact","zoho_desk_create_ticket","zoho_desk_delete_comment","zoho_desk_get_account","zoho_desk_get_agent","zoho_desk_get_attachment","zoho_desk_get_comment","zoho_desk_get_contact","zoho_desk_get_department","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_accounts","zoho_desk_list_agents","zoho_desk_list_comments","zoho_desk_list_contacts","zoho_desk_list_departments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_move_ticket","zoho_desk_search_accounts","zoho_desk_search_contacts","zoho_desk_search_tickets","zoho_desk_send_reply","zoho_desk_update_account","zoho_desk_update_comment","zoho_desk_update_contact","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"]' + '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","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_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_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","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_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","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_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","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_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","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","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","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_aggregated_post_metrics","buffer_get_channel","buffer_get_channels","buffer_get_daily_posting_limits","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_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","checkr_add_report_tag","checkr_apply_report_review_action","checkr_cancel_adverse_action","checkr_cancel_continuous_check","checkr_cancel_invitation","checkr_cancel_subscription","checkr_complete_report","checkr_create_adverse_action","checkr_create_candidate","checkr_create_continuous_check","checkr_create_geo","checkr_create_invitation","checkr_create_report","checkr_create_subscription","checkr_delete_candidate_pii","checkr_delete_geo","checkr_get_account","checkr_get_adverse_action","checkr_get_candidate","checkr_get_continuous_check","checkr_get_document","checkr_get_geo","checkr_get_invitation","checkr_get_node","checkr_get_package","checkr_get_program","checkr_get_report","checkr_get_report_eta","checkr_get_report_progressive_status","checkr_get_report_tags","checkr_get_screening","checkr_get_subscription","checkr_get_verification","checkr_list_adverse_actions","checkr_list_adverse_items","checkr_list_assessments","checkr_list_candidate_documents","checkr_list_candidates","checkr_list_continuous_checks","checkr_list_counties","checkr_list_geos","checkr_list_invitations","checkr_list_nodes","checkr_list_packages","checkr_list_programs","checkr_list_report_addresses","checkr_list_subscriptions","checkr_list_users","checkr_list_verifications","checkr_remove_report_tag","checkr_set_report_tags","checkr_update_candidate","checkr_update_continuous_check","checkr_update_geo","checkr_update_report","checkr_update_subscription","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","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_access_application","cloudflare_create_access_group","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_tunnel","cloudflare_create_worker_route","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_group","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_r2_bucket_cors","cloudflare_delete_ruleset","cloudflare_delete_ruleset_rule","cloudflare_delete_tunnel","cloudflare_delete_worker_route","cloudflare_delete_worker_script","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_access_group","cloudflare_get_access_policy","cloudflare_get_access_service_token","cloudflare_get_dns_record","cloudflare_get_r2_bucket","cloudflare_get_r2_bucket_cors","cloudflare_get_r2_bucket_lifecycle","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_tunnel_token","cloudflare_get_worker_route","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_refresh_access_service_token","cloudflare_revoke_access_service_token","cloudflare_rotate_access_service_token","cloudflare_update_access_application","cloudflare_update_access_group","cloudflare_update_access_policy","cloudflare_update_access_service_token","cloudflare_update_dns_record","cloudflare_update_r2_bucket_cors","cloudflare_update_r2_bucket_lifecycle","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset","cloudflare_update_ruleset_rule","cloudflare_update_tunnel","cloudflare_update_tunnel_configuration","cloudflare_update_worker_route","cloudflare_update_zone","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","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","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","coda_add_custom_domain","coda_add_permission","coda_change_user_role","coda_create_doc","coda_create_folder","coda_create_page","coda_delete_custom_domain","coda_delete_doc","coda_delete_folder","coda_delete_page","coda_delete_page_content","coda_delete_permission","coda_delete_row","coda_delete_rows","coda_export_page","coda_get_acl_settings","coda_get_analytics_last_updated","coda_get_column","coda_get_control","coda_get_custom_domain_provider","coda_get_doc","coda_get_doc_analytics_summary","coda_get_folder","coda_get_formula","coda_get_mutation_status","coda_get_page","coda_get_page_content","coda_get_page_export_status","coda_get_row","coda_get_sharing_metadata","coda_get_table","coda_list_categories","coda_list_columns","coda_list_controls","coda_list_custom_domains","coda_list_doc_analytics","coda_list_docs","coda_list_folder_children","coda_list_folders","coda_list_formulas","coda_list_page_analytics","coda_list_pages","coda_list_permissions","coda_list_rows","coda_list_tables","coda_list_workspace_members","coda_list_workspace_roles","coda_publish_doc","coda_push_button","coda_resolve_browser_link","coda_search_principals","coda_trigger_automation","coda_unpublish_doc","coda_update_acl_settings","coda_update_doc","coda_update_folder","coda_update_page","coda_update_row","coda_upsert_rows","coda_whoami","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_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","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_all_runs","databricks_cancel_run","databricks_cancel_statement","databricks_create_cluster","databricks_create_job","databricks_create_warehouse","databricks_delete_cluster","databricks_delete_job","databricks_delete_run","databricks_delete_warehouse","databricks_edit_cluster","databricks_edit_warehouse","databricks_execute_sql","databricks_genie_agent_ask","databricks_genie_agent_cancel","databricks_genie_agent_list_items","databricks_genie_ask","databricks_genie_create_space","databricks_genie_delete_conversation","databricks_genie_delete_message","databricks_genie_delete_space","databricks_genie_download_visualization","databricks_genie_execute_query","databricks_genie_get_message","databricks_genie_get_query_result","databricks_genie_get_space","databricks_genie_list_conversations","databricks_genie_list_messages","databricks_genie_list_spaces","databricks_genie_send_feedback","databricks_genie_update_space","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_get_statement_chunk","databricks_get_warehouse","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_repair_run","databricks_reset_job","databricks_resize_cluster","databricks_restart_cluster","databricks_run_job","databricks_start_cluster","databricks_start_warehouse","databricks_stop_warehouse","databricks_submit_run","databricks_terminate_cluster","databricks_update_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","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_download_v2","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_get_qr_code_v2","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","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","ec2_allocate_address","ec2_associate_address","ec2_authorize_security_group_ingress","ec2_create_snapshot","ec2_create_tags","ec2_delete_snapshot","ec2_delete_tags","ec2_delete_volume","ec2_describe_addresses","ec2_describe_availability_zones","ec2_describe_images","ec2_describe_instance_status","ec2_describe_instances","ec2_describe_internet_gateways","ec2_describe_key_pairs","ec2_describe_nat_gateways","ec2_describe_network_acls","ec2_describe_network_interfaces","ec2_describe_regions","ec2_describe_route_tables","ec2_describe_security_groups","ec2_describe_snapshots","ec2_describe_subnets","ec2_describe_volumes","ec2_describe_vpc_endpoints","ec2_describe_vpc_peering_connections","ec2_describe_vpcs","ec2_disassociate_address","ec2_reboot_instances","ec2_release_address","ec2_revoke_security_group_ingress","ec2_start_instances","ec2_stop_instances","ec2_terminate_instances","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","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","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","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_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","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_enrich","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","fivetran_cancel_transformation","fivetran_create_connect_card","fivetran_create_connection","fivetran_create_destination","fivetran_create_group","fivetran_delete_connection","fivetran_get_account_info","fivetran_get_connection","fivetran_get_connection_schema_config","fivetran_get_connection_warnings","fivetran_get_connector_type","fivetran_get_destination","fivetran_get_group","fivetran_get_sync_history","fivetran_get_transformation","fivetran_list_connections","fivetran_list_connector_types","fivetran_list_destinations","fivetran_list_groups","fivetran_list_transformations","fivetran_list_users","fivetran_reload_connection_schema","fivetran_resync_connection","fivetran_resync_tables","fivetran_run_connection_setup_tests","fivetran_run_destination_setup_tests","fivetran_run_transformation","fivetran_sync_connection","fivetran_update_connection","fivetran_update_connection_schema","fivetran_update_connection_table","fivetran_update_schema_config","flint_create_task","flint_generate_pages","flint_get_task","flow_add_link","flow_create_branch","flow_create_comment","flow_create_entities","flow_create_entity","flow_delete_comment","flow_delete_entity","flow_filter_entities","flow_get_comment","flow_get_data_model","flow_get_entities","flow_get_entity_history","flow_get_project","flow_list_branches","flow_list_comments","flow_list_entities","flow_list_projects","flow_remove_link","flow_restore_entity","flow_search_entities","flow_set_value","flow_set_values","flow_update_comment","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","gauge_add_tags_to_prompts","gauge_bulk_update_prompts","gauge_create_prompts","gauge_create_tag","gauge_create_topic","gauge_delete_tag","gauge_get_answer","gauge_get_category","gauge_get_current_credential","gauge_get_organization","gauge_get_prompt","gauge_get_query_metadata","gauge_get_tag","gauge_get_topic","gauge_list_answers","gauge_list_brands","gauge_list_categories","gauge_list_citations","gauge_list_organizations","gauge_list_prompts","gauge_list_tags","gauge_list_topics","gauge_query","gauge_remove_tags_from_prompts","gauge_rename_topic","gauge_update_prompt","gauge_update_tag","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_respond","google_calendar_respond_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_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","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_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","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_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_apply_candidate_tag_v2","greenhouse_convert_prospect_v2","greenhouse_create_application_v2","greenhouse_create_attachment_v2","greenhouse_create_candidate_tag_v2","greenhouse_create_candidate_v2","greenhouse_create_note_v2","greenhouse_delete_application_v2","greenhouse_delete_attachment_v2","greenhouse_delete_candidate_v2","greenhouse_get_application","greenhouse_get_application_v2","greenhouse_get_candidate","greenhouse_get_candidate_v2","greenhouse_get_job","greenhouse_get_job_v2","greenhouse_get_user","greenhouse_get_user_v2","greenhouse_hire_application_v2","greenhouse_list_application_stages_v2","greenhouse_list_applications","greenhouse_list_applications_v2","greenhouse_list_applied_candidate_tags_v2","greenhouse_list_attachments_v2","greenhouse_list_candidate_educations_v2","greenhouse_list_candidate_employments_v2","greenhouse_list_candidate_tags_v2","greenhouse_list_candidates","greenhouse_list_candidates_v2","greenhouse_list_close_reasons_v2","greenhouse_list_custom_field_options_v2","greenhouse_list_custom_fields_v2","greenhouse_list_departments","greenhouse_list_departments_v2","greenhouse_list_email_templates_v2","greenhouse_list_interviews_v2","greenhouse_list_job_hiring_managers_v2","greenhouse_list_job_interview_stages_v2","greenhouse_list_job_owners_v2","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_jobs_v2","greenhouse_list_notes_v2","greenhouse_list_offers_v2","greenhouse_list_offices","greenhouse_list_offices_v2","greenhouse_list_openings_v2","greenhouse_list_prospect_pool_stages_v2","greenhouse_list_prospect_pools_v2","greenhouse_list_referrers_v2","greenhouse_list_rejection_reasons_v2","greenhouse_list_scorecards_v2","greenhouse_list_sources_v2","greenhouse_list_users","greenhouse_list_users_v2","greenhouse_move_application_v2","greenhouse_reject_application_v2","greenhouse_remove_candidate_tag_v2","greenhouse_unreject_application_v2","greenhouse_update_application_v2","greenhouse_update_candidate_v2","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","helpscout_assign_conversation","helpscout_create_conversation","helpscout_create_customer","helpscout_create_customer_thread","helpscout_create_note","helpscout_create_reply","helpscout_delete_conversation","helpscout_get_conversation","helpscout_get_current_user","helpscout_get_customer","helpscout_get_organization","helpscout_get_saved_reply","helpscout_list_conversations","helpscout_list_custom_fields","helpscout_list_customer_properties","helpscout_list_customers","helpscout_list_folders","helpscout_list_mailboxes","helpscout_list_organizations","helpscout_list_saved_replies","helpscout_list_tags","helpscout_list_teams","helpscout_list_threads","helpscout_list_users","helpscout_list_workflows","helpscout_move_conversation","helpscout_run_workflow","helpscout_set_conversation_tags","helpscout_snooze_conversation","helpscout_unassign_conversation","helpscout_unsnooze_conversation","helpscout_update_conversation_customer","helpscout_update_conversation_status","helpscout_update_conversation_subject","helpscout_update_custom_fields","helpscout_update_customer","helpscout_update_customer_properties","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","higgsfield_cancel_request","higgsfield_create_character","higgsfield_delete_character","higgsfield_delete_request","higgsfield_generate","higgsfield_generate_soul_image","higgsfield_get_character","higgsfield_get_request_status","higgsfield_list_characters","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_policy","iam_get_role","iam_get_user","iam_list_access_keys","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","iam_update_access_key","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_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","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_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","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_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","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","intune_get_compliance_policy","intune_get_detected_app","intune_get_device","intune_get_device_configuration","intune_list_app_devices","intune_list_compliance_policies","intune_list_compliance_policy_device_statuses","intune_list_detected_apps","intune_list_device_configurations","intune_list_devices","intune_reboot_device","intune_remote_lock_device","intune_retire_device","intune_sync_device","jama_create_comment","jama_create_item","jama_create_relationship","jama_delete_item","jama_delete_relationship","jama_get_baseline","jama_get_current_user","jama_get_filter","jama_get_item","jama_get_item_type","jama_get_item_version","jama_get_project","jama_get_relationship","jama_get_test_cycle","jama_get_test_plan","jama_get_test_run","jama_list_baseline_items","jama_list_baselines","jama_list_downstream_items","jama_list_filter_results","jama_list_filters","jama_list_item_children","jama_list_item_comments","jama_list_item_types","jama_list_item_versions","jama_list_items","jama_list_pick_list_options","jama_list_pick_lists","jama_list_projects","jama_list_relationship_types","jama_list_relationships","jama_list_releases","jama_list_tags","jama_list_test_cycles","jama_list_test_plans","jama_list_test_runs","jama_list_upstream_items","jama_search_items","jama_update_item","jama_update_test_run","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_create_sprint","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","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","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_get_content_v2","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","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","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","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","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","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_user_memberships","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","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_download_file_v2","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_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_calculate","microsoft_excel_clear_range","microsoft_excel_close_session","microsoft_excel_create_session","microsoft_excel_create_table","microsoft_excel_delete_table","microsoft_excel_delete_table_row","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_get_table","microsoft_excel_get_table_row","microsoft_excel_get_worksheet","microsoft_excel_list_table_rows","microsoft_excel_list_tables","microsoft_excel_list_worksheets","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_refresh_session","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_update_table","microsoft_excel_update_table_row","microsoft_excel_update_worksheet","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","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","monaco_add_audience_contacts","monaco_add_campaign_audiences","monaco_create_audience","monaco_create_contact","monaco_create_opportunity","monaco_create_tag","monaco_create_task","monaco_delete_account","monaco_delete_contact","monaco_delete_opportunity","monaco_enroll_campaign_contacts","monaco_get_account","monaco_get_audience","monaco_get_campaign","monaco_get_contact","monaco_get_conversation","monaco_get_current_user","monaco_get_field_schemas","monaco_get_meeting","monaco_get_opportunity","monaco_get_sequence","monaco_get_task","monaco_link_opportunity_contacts","monaco_list_accounts","monaco_list_audience_contacts","monaco_list_audiences","monaco_list_campaigns","monaco_list_contacts","monaco_list_meetings","monaco_list_messages","monaco_list_opportunities","monaco_list_opportunity_contacts","monaco_list_pipelines","monaco_list_sequences","monaco_list_tags","monaco_list_tasks","monaco_list_users","monaco_remove_audience_contacts","monaco_unlink_opportunity_contacts","monaco_update_account","monaco_update_contact","monaco_update_opportunity","monaco_update_sequence","monaco_update_task","monaco_upsert_account","monaco_upsert_contact","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","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_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","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","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_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","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","oracle_fusion_financials_apply_receivables_receipt","oracle_fusion_financials_approve_receivables_invoice","oracle_fusion_financials_create_expense_line","oracle_fusion_financials_create_expense_report","oracle_fusion_financials_create_receivables_credit_memo","oracle_fusion_financials_create_receivables_invoice","oracle_fusion_financials_create_receivables_receipt","oracle_fusion_financials_get_expense_line","oracle_fusion_financials_get_expense_report","oracle_fusion_financials_get_gl_journal_batch","oracle_fusion_financials_get_gl_ledger","oracle_fusion_financials_get_payables_invoice","oracle_fusion_financials_get_payables_invoice_line","oracle_fusion_financials_get_payables_payment","oracle_fusion_financials_get_receivables_credit_memo","oracle_fusion_financials_get_receivables_invoice","oracle_fusion_financials_get_receivables_invoice_installment","oracle_fusion_financials_get_receivables_invoice_line","oracle_fusion_financials_get_receivables_receipt","oracle_fusion_financials_list_expense_lines","oracle_fusion_financials_list_expense_reports","oracle_fusion_financials_list_gl_balances","oracle_fusion_financials_list_gl_journal_batches","oracle_fusion_financials_list_gl_ledgers","oracle_fusion_financials_list_payables_invoice_lines","oracle_fusion_financials_list_payables_invoices","oracle_fusion_financials_list_payables_payments","oracle_fusion_financials_list_receivables_credit_memos","oracle_fusion_financials_list_receivables_invoice_installments","oracle_fusion_financials_list_receivables_invoice_lines","oracle_fusion_financials_list_receivables_invoices","oracle_fusion_financials_list_receivables_receipts","oracle_fusion_financials_submit_expense_report","oracle_fusion_financials_update_expense_line","oracle_fusion_financials_update_expense_report","oracle_fusion_financials_update_receivables_invoice","oracledb_delete","oracledb_execute","oracledb_insert","oracledb_introspect","oracledb_query","oracledb_update","otter_create_conversation","otter_get_conversation","otter_get_conversation_audio","otter_get_workspace","otter_list_channel_members","otter_list_channels","otter_list_conversations","otter_list_workspace_conversations","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","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","plane_archive_project","plane_archive_work_item","plane_confirm_attachment_upload","plane_create_attachment_upload","plane_create_comment","plane_create_cycle","plane_create_label","plane_create_link","plane_create_module","plane_create_project","plane_create_project_page","plane_create_state","plane_create_webhook","plane_create_work_item","plane_delete_attachment","plane_delete_comment","plane_delete_cycle","plane_delete_label","plane_delete_link","plane_delete_module","plane_delete_project","plane_delete_project_page","plane_delete_state","plane_delete_webhook","plane_delete_work_item","plane_get_attachment","plane_get_comment","plane_get_current_user","plane_get_cycle","plane_get_label","plane_get_link","plane_get_module","plane_get_project","plane_get_project_page","plane_get_project_summary","plane_get_state","plane_get_webhook","plane_get_work_item","plane_get_work_item_by_identifier","plane_list_attachments","plane_list_comments","plane_list_cycles","plane_list_labels","plane_list_links","plane_list_modules","plane_list_project_pages","plane_list_projects","plane_list_states","plane_list_webhooks","plane_list_work_items","plane_list_workspace_work_items","plane_manage_cycle_work_items","plane_manage_module_work_items","plane_regenerate_webhook_secret","plane_transfer_cycle_work_items","plane_unarchive_project","plane_unarchive_work_item","plane_update_comment","plane_update_cycle","plane_update_label","plane_update_link","plane_update_module","plane_update_project","plane_update_project_page","plane_update_state","plane_update_webhook","plane_update_work_item","planetscale_close_deploy_request","planetscale_create_backup","planetscale_create_branch","planetscale_create_deploy_request","planetscale_delete_branch","planetscale_get_backup","planetscale_get_branch","planetscale_get_database","planetscale_get_deploy_request","planetscale_list_backups","planetscale_list_branches","planetscale_list_databases","planetscale_list_deploy_requests","planetscale_queue_deploy_request","planetscale_review_deploy_request","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","powerbi_execute_query","powerbi_get_dataset","powerbi_get_refresh_history","powerbi_get_report","powerbi_list_datasets","powerbi_list_reports","powerbi_list_workspaces","powerbi_refresh_dataset","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","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","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","ramp_create_memo","ramp_get_balance","ramp_get_bill","ramp_get_business","ramp_get_memo","ramp_get_physical_card","ramp_get_reimbursement","ramp_get_transaction","ramp_get_user","ramp_get_vendor","ramp_get_virtual_card","ramp_list_bills","ramp_list_physical_cards","ramp_list_reimbursements","ramp_list_transactions","ramp_list_users","ramp_list_vendors","ramp_list_virtual_cards","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","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","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","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","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_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","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_download_v2","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_create_product_variants","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_fulfillment","shopify_get_fulfillment_order","shopify_get_inventory_level","shopify_get_job","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_fulfillment_orders","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_fulfillment_tracking","shopify_update_order","shopify_update_product","shopify_update_product_variants","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_bookmark","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_close_conversation","slack_create_channel_canvas","slack_create_conversation","slack_create_user_group","slack_delete_canvas","slack_delete_file","slack_delete_message","slack_delete_scheduled_message","slack_disable_user_group","slack_download","slack_edit_bookmark","slack_edit_canvas","slack_enable_user_group","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_dnd_info","slack_get_file_info","slack_get_message","slack_get_permalink","slack_get_reactions","slack_get_team_dnd_info","slack_get_team_info","slack_get_team_profile","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_get_user_profile","slack_invite_to_conversation","slack_join_conversation","slack_kick_conversation","slack_leave_conversation","slack_list_bookmarks","slack_list_canvases","slack_list_channels","slack_list_emoji","slack_list_files","slack_list_members","slack_list_pins","slack_list_reactions","slack_list_scheduled_messages","slack_list_user_conversations","slack_list_user_group_members","slack_list_user_groups","slack_list_users","slack_lists_access_delete","slack_lists_access_set","slack_lists_create","slack_lists_download_get","slack_lists_download_start","slack_lists_items_create","slack_lists_items_delete","slack_lists_items_delete_multiple","slack_lists_items_info","slack_lists_items_list","slack_lists_items_update","slack_lists_update","slack_lookup_canvas_sections","slack_lookup_user_by_email","slack_mark_conversation_read","slack_message","slack_message_reader","slack_open_conversation","slack_open_view","slack_pin_message","slack_publish_view","slack_push_view","slack_remove_bookmark","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_revoke_canvas_access","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_set_user_presence","slack_share_canvas","slack_unarchive_conversation","slack_unfurl_links","slack_unpin_message","slack_update_message","slack_update_user_group","slack_update_user_group_members","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_cortex_analyst_ask","snowflake_cortex_analyst_feedback","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","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_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","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_download_file_v2","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","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","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_approve_user","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_device_attribute","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_attributes","tailscale_get_device_routes","tailscale_get_dns_configuration","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_get_split_dns","tailscale_get_tailnet_settings","tailscale_get_user","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_restore_user","tailscale_set_acl","tailscale_set_device_attribute","tailscale_set_device_name","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_configuration","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_set_split_dns","tailscale_set_user_role","tailscale_suspend_user","tailscale_update_device_key","tailscale_update_split_dns","tailscale_update_tailnet_settings","tailscale_validate_acl","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","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","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_higgsfield","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","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","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","youcom_answer","youcom_finance_research","youcom_get_account_balance","youcom_get_contents","youcom_get_research_task","youcom_research","youcom_search","youcom_search_images","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_create_account","zoho_desk_create_contact","zoho_desk_create_ticket","zoho_desk_delete_comment","zoho_desk_get_account","zoho_desk_get_agent","zoho_desk_get_attachment","zoho_desk_get_comment","zoho_desk_get_contact","zoho_desk_get_department","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_accounts","zoho_desk_list_agents","zoho_desk_list_comments","zoho_desk_list_contacts","zoho_desk_list_departments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_move_ticket","zoho_desk_search_accounts","zoho_desk_search_contacts","zoho_desk_search_tickets","zoho_desk_send_reply","zoho_desk_update_account","zoho_desk_update_comment","zoho_desk_update_contact","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 8e1ae7d98e9..7e8fd53d69f 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)"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"affinity_batch_update_entity_fields":{"id":"affinity_batch_update_entity_fields","name":"Affinity Batch Update Entity Fields","description":"Write up to 100 non-list field values on one company or person in a single request.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the fields on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_batch_update_list_entry_fields":{"id":"affinity_batch_update_list_entry_fields","name":"Affinity Batch Update List Entry Fields","description":"Write up to 100 field values on one list row in a single request. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_create_list":{"id":"affinity_create_list","name":"Affinity Create List","description":"Create a list. Its type fixes which entities it can hold, and the API key holder becomes its creator and owner.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the new list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Entity kind the list holds: company, opportunity, or person"},"isPublic":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether everyone in the organization can see the list"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_create_list_field_dropdown_option":{"id":"affinity_create_list_field_dropdown_option","name":"Affinity Create List Field Dropdown Option","description":"Add a selectable option to a dropdown field on a list. A ranked or status option also needs a rank and a color.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Kind of option to create, matching the field. dropdown takes only a label; ranked-dropdown also requires rank and color; status-dropdown additionally requires a status category. Sending a field the kind does not accept is rejected"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The option label"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_create_merge":{"id":"affinity_create_merge","name":"Affinity Create Merge","description":"Fold a duplicate company or person into the record you are keeping. The merge runs asynchronously — poll the returned task to see it finish. Requires the \\"Manage duplicates\\" permission and an admin role.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to merge: companies or persons"},"primaryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to keep"},"duplicateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the duplicate record to fold in"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_create_note":{"id":"affinity_create_note","name":"Affinity Create Note","description":"Write a note — attached to companies, persons, and opportunities, anchored to a meeting, call, or chat message, or posted as a reply to an existing note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Note shape: entities to attach it to records, interaction to anchor it to a meeting, call, or chat message, or user-reply to reply to a note"},"html":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Companies to attach the note to, e.g. [1, 2]. Not used on a reply"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Persons to attach the note to, e.g. [1, 2]. Not used on a reply"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Opportunities to attach the note to, e.g. [1, 2]. Not used on a reply"},"interactionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The interaction to anchor the note to. Required for an interaction note"},"interactionType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Kind of the anchoring interaction: meeting, call, or chat-message. Required for an interaction note"},"parentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The note being replied to. Required for a user-reply note"},"creatorId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Attribute the note to another internal person. Defaults to the API key holder"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdate the note to this ISO 8601 timestamp"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_create_reminder":{"id":"affinity_create_reminder","name":"Affinity Create Reminder","description":"Create a reminder on one company, person, or opportunity. A recurring reminder resets whenever the chosen signal happens instead of firing once.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"one-time to fire once, or recurring to reset on a signal"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What the reminder is about: company, person, or opportunity"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"dueDate":{"type":"string","required":false,"visibility":"user-or-llm","description":"When the reminder is due, as an ISO 8601 timestamp. Required for a one-time reminder; on a recurring one Affinity computes it from the period when omitted"},"content":{"type":"string","required":false,"visibility":"user-or-llm","description":"What the reminder says"},"ownerId":{"type":"string","required":true,"visibility":"user-or-llm","description":"User the reminder is assigned to. Must be an internal user. The API key holder is recorded as the creator, which is a separate field"},"resetTrigger":{"type":"string","required":false,"visibility":"user-or-llm","description":"What restarts a recurring reminder: interaction, email, or event. Required when the type is recurring"},"periodDays":{"type":"number","required":false,"visibility":"user-or-llm","description":"Days between firings of a recurring reminder. Required when the type is recurring"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_delete_list_field_dropdown_option":{"id":"affinity_delete_list_field_dropdown_option","name":"Affinity Delete List Field Dropdown Option","description":"Permanently delete a dropdown option on a list field. Every list entry currently set to it is cleared, and those values cannot be recovered.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to delete"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_delete_note":{"id":"affinity_delete_note","name":"Affinity Delete Note","description":"Delete a note you created. Deleting a root note also deletes its replies; deleting a reply removes only that reply.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to delete"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_company":{"id":"affinity_get_company","name":"Affinity Get Company","description":"Look up one company by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"companyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The company ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_current_user":{"id":"affinity_get_current_user","name":"Affinity Get Current User","description":"Verify an Affinity API key and return the tenant, the user behind the key, and the scopes the grant carries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_entity_field_value":{"id":"affinity_get_entity_field_value","name":"Affinity Get Entity Field Value","description":"Read one non-list field value from a company or person.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read the field from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_list":{"id":"affinity_get_list","name":"Affinity Get List","description":"Read one list — its name, type, owner, and privacy setting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_list_entry":{"id":"affinity_get_list_entry","name":"Affinity Get List Entry","description":"Read one row of a list with its entity. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_list_entry_field":{"id":"affinity_get_list_entry_field","name":"Affinity Get List Entry Field","description":"Read one field value on a list row.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_list_field_dropdown_option":{"id":"affinity_get_list_field_dropdown_option","name":"Affinity Get List Field Dropdown Option","description":"Read one dropdown option on a list field.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_merge":{"id":"affinity_get_merge","name":"Affinity Get Merge","description":"Read the status of one company or person merge, including why it failed if it did.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge to read: companies or persons"},"mergeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_merge_task":{"id":"affinity_get_merge_task","name":"Affinity Get Merge Task","description":"Read one merge task and how its merges are progressing. Poll this after starting a merge.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge task to read: companies or persons"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge task ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_note":{"id":"affinity_get_note","name":"Affinity Get Note","description":"Read one note with its body, author, mentions, and attached records.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_opportunity":{"id":"affinity_get_opportunity","name":"Affinity Get Opportunity","description":"Read one opportunity and the list it belongs to. Its field data lives on the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"opportunityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The opportunity ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_person":{"id":"affinity_get_person","name":"Affinity Get Person","description":"Look up one person by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"personId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The person ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_saved_view":{"id":"affinity_get_saved_view","name":"Affinity Get Saved View","description":"Read one saved view — its name, kind, and creation date.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_transcript":{"id":"affinity_get_transcript","name":"Affinity Get Transcript","description":"Read one transcript with its first 100 fragments. Page the fragments endpoint for a longer meeting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_get_user":{"id":"affinity_get_user","name":"Affinity Get User","description":"Read one internal user. A user and their person record share the same numeric ID, so a person ID works here.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"userId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The user ID, which is also their person ID"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_calls":{"id":"affinity_list_calls","name":"Affinity List Calls","description":"Page through logged calls and their participants. Only calls the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_chat_messages":{"id":"affinity_list_chat_messages","name":"Affinity List Chat Messages","description":"Page through logged chat messages and their participants. Only messages the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_companies":{"id":"affinity_list_companies","name":"Affinity List Companies","description":"Page through companies. Companies come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these company IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_coworker_connections":{"id":"affinity_list_coworker_connections","name":"Affinity List Coworker Connections","description":"Find warm paths into a company through shared work history: who in your Affinity data once worked alongside the people you want to reach. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_emails":{"id":"affinity_list_emails","name":"Affinity List Emails","description":"Page through email metadata — subject, participants, and timestamps. Affinity never exposes email bodies through the API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_entity_field_values":{"id":"affinity_list_entity_field_values","name":"Affinity List Entity Field Values","description":"Page through a company\'s or person\'s non-list field values. List fields are not returned here — read those through the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read field values from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_entity_list_entries":{"id":"affinity_list_entity_list_entries","name":"Affinity List Entity List Entries","description":"Page through a company\'s or person\'s rows across every list, each carrying that list\'s field values and when the entity was added.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the rows of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_entity_lists":{"id":"affinity_list_entity_lists","name":"Affinity List Entity Lists","description":"List every list a company or person appears on that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the lists of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_entity_notes":{"id":"affinity_list_entity_notes","name":"Affinity List Entity Notes","description":"List the notes relevant to one company, person, or opportunity — directly attached notes plus notes reaching it through its people and meetings.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity the notes hang off: companies, persons, or opportunities"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_entity_relationships":{"id":"affinity_list_entity_relationships","name":"Affinity List Entity Relationships","description":"List who knows a company or person, scored 0.0 to 1.0 by how much the two actually interact. Strongest first by default.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up relationships for: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on interactionScore only, e.g. \\"interactionScore>=0.5\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"interactionScore\\"] for weakest first, [\\"-interactionScore\\"] for strongest first (the default)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_field_dropdown_options":{"id":"affinity_list_field_dropdown_options","name":"Affinity List Field Dropdown Options","description":"List the selectable options on a dropdown or ranked-dropdown company or person field. Writing such a field needs the option ID, not its text.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which field family the field belongs to: companies or persons"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown or ranked-dropdown field ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_field_metadata":{"id":"affinity_list_field_metadata","name":"Affinity List Field Metadata","description":"List the non-list company or person fields, with the value type, filter operators, and sort support of each. Start here to find the Field IDs the read and write tools take.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which fields to describe: companies or persons"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Status\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_field_value_changes":{"id":"affinity_list_field_value_changes","name":"Affinity List Field Value Changes","description":"Page through field value changes across the whole workspace. Built for delta sync: follow nextCursor to the end of a run, then resume from the last cursor next time.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, listEntry.id, changer.id, changedAt, or actionType. Resume a sync with e.g. \\"changedAt>2026-06-01T12:00:00Z\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"changedAt\\"] for oldest first (the default), [\\"-changedAt\\"] for newest first"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_investor_executive_connections":{"id":"affinity_list_investor_executive_connections","name":"Affinity List Investor Executive Connections","description":"Find warm paths into a company through investment history: which investors in your Affinity data backed a company the people you want to reach once led. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_list_entries":{"id":"affinity_list_list_entries","name":"Affinity List List Entries","description":"Page through the rows of a list. Rows come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_list_entry_field_value_changes":{"id":"affinity_list_list_entry_field_value_changes","name":"Affinity List List Entry Field Value Changes","description":"Page through the history of one list row — who changed which field, when, and to what.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, changer.id, changedAt, or actionType, e.g. \\"field.id=field-1234\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_list_entry_fields":{"id":"affinity_list_list_entry_fields","name":"Affinity List List Entry Fields","description":"Page through every field value on one list row, including the list-specific columns. All fields are returned unless narrowed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, list, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_list_field_dropdown_options":{"id":"affinity_list_list_field_dropdown_options","name":"Affinity List List Field Dropdown Options","description":"List the selectable options on a dropdown, ranked-dropdown, or status-dropdown field of a list.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_list_fields":{"id":"affinity_list_list_fields","name":"Affinity List List Fields","description":"List the fields available on one list, including its list-specific columns. Use these Field IDs when reading or writing list entries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Stage\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_lists":{"id":"affinity_list_lists","name":"Affinity List Lists","description":"Page through the lists in the organization that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive substring match on the list name"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_meetings":{"id":"affinity_list_meetings","name":"Affinity List Meetings","description":"Page through past and upcoming meetings with their organizer and attendees.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_merge_tasks":{"id":"affinity_list_merge_tasks","name":"Affinity List Merge Tasks","description":"Page through merge tasks, each summarizing how many of its merges are in progress, succeeded, or failed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge tasks to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on status only, e.g. \\"status=in-progress\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_merges":{"id":"affinity_list_merges","name":"Affinity List Merges","description":"Page through the company or person merges the organization has run, with the status and the records involved in each.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merges to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over status or taskId, e.g. \\"status=failed\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_note_attached_companies":{"id":"affinity_list_note_attached_companies","name":"Affinity List Note Attached Companies","description":"List the companies directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_note_attached_opportunities":{"id":"affinity_list_note_attached_opportunities","name":"Affinity List Note Attached Opportunities","description":"List the opportunities directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_note_attached_persons":{"id":"affinity_list_note_attached_persons","name":"Affinity List Note Attached Persons","description":"List the persons directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_note_replies":{"id":"affinity_list_note_replies","name":"Affinity List Note Replies","description":"Page through the replies on one note, including AI Notetaker replies.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID whose replies to read"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_notes":{"id":"affinity_list_notes","name":"Affinity List Notes","description":"Page through every note the caller can see. Replies are excluded.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_opportunities":{"id":"affinity_list_opportunities","name":"Affinity List Opportunities","description":"Page through opportunities. Field data lives on the list entry, not here — read it through the list or saved view the opportunity belongs to.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these opportunity IDs, e.g. [1, 2, 3]"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_persons":{"id":"affinity_list_persons","name":"Affinity List Persons","description":"Page through persons. Persons come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these person IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_reminders":{"id":"affinity_list_reminders","name":"Affinity List Reminders","description":"Page through the reminders the caller can see. Filter by status to surface what is overdue.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_saved_view_entries":{"id":"affinity_list_saved_view_entries","name":"Affinity List Saved View Entries","description":"Page through the rows of a saved view. The view\'s own filters and columns decide which rows and which field data come back.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_saved_views":{"id":"affinity_list_saved_views","name":"Affinity List Saved Views","description":"List the saved views on a list that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_transcript_fragments":{"id":"affinity_list_transcript_fragments","name":"Affinity List Transcript Fragments","description":"Page through everything said in a meeting, segment by segment with the speaker.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_transcripts":{"id":"affinity_list_transcripts","name":"Affinity List Transcripts","description":"Page through meeting transcript metadata. Read one transcript to get what was actually said.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_list_users":{"id":"affinity_list_users","name":"Affinity List Users","description":"Page through the internal users in the organization. Email addresses and roles are returned only to callers with the \\"Manage Users\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive match across first name, last name, and primary email"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over id or status, e.g. \\"status=active\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_search_companies":{"id":"affinity_search_companies","name":"Affinity Search Companies","description":"Search companies by filters, sorts, and a free-text term. Requires the \\"Export All Organizations directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_search_files":{"id":"affinity_search_files","name":"Affinity Search Files","description":"Search files by keyword, ordered by relevance. Narrow to specific files or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these file IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s files. Cannot be combined with file IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of files to return, 1-100. Defaults to 20"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_search_list_entries":{"id":"affinity_search_list_entries","name":"Affinity Search List Entries","description":"Search the rows of one list by filters, sorts, and a free-text term. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID to search"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_search_notes":{"id":"affinity_search_notes","name":"Affinity Search Notes","description":"Search notes by keyword, ordered by relevance. Narrow to specific notes or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these note IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s notes. Cannot be combined with note IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of notes to return, 1-100. Defaults to 20"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_search_persons":{"id":"affinity_search_persons","name":"Affinity Search Persons","description":"Search persons by filters, sorts, and a free-text term. Requires the \\"Export All People directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_semantic_search":{"id":"affinity_semantic_search","name":"Affinity Semantic Search","description":"Find companies from a description in plain language — industry, technology, stage, or business model. Currently searches companies only.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to look for, in plain language, e.g. \\"climate tech companies in our pipeline\\". Up to 500 characters"},"listIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to companies on these lists, e.g. [1, 2]"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of companies to return, 1-100. Defaults to 100"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_update_entity_field_value":{"id":"affinity_update_entity_field_value","name":"Affinity Update Entity Field Value","description":"Write one non-list field value on a company or person. The value type must match how the field is defined.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the field on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_update_list_entry_field":{"id":"affinity_update_list_entry_field","name":"Affinity Update List Entry Field","description":"Write one field value on a list row. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_update_list_field_dropdown_option":{"id":"affinity_update_list_field_dropdown_option","name":"Affinity Update List Field Dropdown Option","description":"Change a dropdown option on a list field. Every field is optional — supply only what should change, and only fields the option\'s kind actually has.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to update"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement option label. Supply at least one field to change"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"affinity_update_note":{"id":"affinity_update_note","name":"Affinity Update Note","description":"Rewrite a note\'s body or replace which records it is attached to. Each list of IDs replaces that association wholesale, an empty list clears it, and omitting one leaves it untouched. A note\'s type cannot be changed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to update"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached companies, e.g. [1, 2]. Send [] to detach every company; omit to leave them unchanged"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached persons, e.g. [1, 2]. Send [] to detach every person; omit to leave them unchanged"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached opportunities, e.g. [1, 2]. Send [] to detach every opportunity; omit to leave them unchanged"}},"oauth":{"required":false,"provider":"affinity","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentmail","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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."}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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."}},"oauth":{"required":false,"provider":"agentphone","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"agiloft_async_status":{"id":"agiloft_async_status","name":"Agiloft Async Status","description":"Check whether an asynchronous Agiloft call, such as a run action button, has completed.","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 the asynchronous call was made against"},"callbackId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Callback ID returned by the asynchronous call, e.g. from Run Action Button"}},"hostedApiKey":"none"},"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":true,"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)"},"overwrite":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Replace the contents of the field instead of adding another file to it"}},"hostedApiKey":"none"},"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"}},"hostedApiKey":"none"},"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\\"})"}},"hostedApiKey":"none"},"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"},"substituteIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated IDs of records that adopt the dependants of the deleted record. Read only when the delete rule is REPLACE_WITH_ANOTHER."},"deleteRule":{"type":"string","required":false,"visibility":"user-or-llm","description":"How to treat records that depend on this one: ERROR_IF_DEPENDANTS (default — fails rather than cascading), APPLY_DELETE_WHERE_POSSIBLE, DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK, APPLY_UNLINK, UNLINK_WHERE_POSSIBLE_OTHERWISE_DELETE, or REPLACE_WITH_ANOTHER"}},"hostedApiKey":"none"},"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\\")"}},"hostedApiKey":"none"},"agiloft_list_tables":{"id":"agiloft_list_tables","name":"Agiloft List Tables","description":"List the tables and fields in an Agiloft knowledge base, to discover the logical names other operations need.","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":false,"visibility":"user-or-llm","description":"Logical name of a single table to describe (e.g., \\"contacts\\"). Leave empty to list every table in the knowledge base."},"includeLinkedInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the source table and column behind each linked field"},"skipColumnsInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Return table names only, omitting field details, for a much smaller response"}},"hostedApiKey":"none"},"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\\""},"force":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Unlock only: release a lock held by another user."}},"hostedApiKey":"none"},"agiloft_nlp_search":{"id":"agiloft_nlp_search","name":"Agiloft Natural Language Search","description":"Search Agiloft records by describing what you want in plain language, such as \\"active NDAs submitted last month\\".","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"},"nlpQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The request in plain language, e.g. \\"Show me open, high-priority contracts\\". Structured field filters are not accepted — use Search Records for those."},"fields":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated field names to return, e.g. \\"id, contract_title1, company_name\\""},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number, starting from 0"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Records per page"}},"hostedApiKey":"none"},"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"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"agiloft_run_action_button":{"id":"agiloft_run_action_button","name":"Agiloft Run Action Button","description":"Run an action button on an Agiloft record, such as an approval or send-for-signature step.","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\\", \\"case\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to run the action button on"},"actionButtonField":{"type":"string","required":true,"visibility":"user-or-llm","description":"Logical name of the field holding the action button (e.g., \\"ab_field\\")"}},"hostedApiKey":"none"},"agiloft_saved_search":{"id":"agiloft_saved_search","name":"Agiloft Saved Search","description":"List the 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":"Logical table name to list saved searches for (e.g., \\"contract\\")"}},"hostedApiKey":"none"},"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":false,"visibility":"user-or-llm","description":"Ad hoc EWSearch query. Combine conditions with && (and) or || (or) and quote every value — e.g. \\"summary~=\'test\'&&priority=\'High\'\\". Required unless a saved search is given."},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Label of a saved search defined on the table (e.g., \\"C: Status is Closed\\"). Can be combined with a query to narrow it further."},"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 treats 0 as \\"all records\\", so leave it unset or use a positive value to keep result sizes bounded."}},"hostedApiKey":"none"},"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\'\\"). EWSelect has no page size and returns every matching ID, so append a database limit such as \\"limit 0,200\\" to bound the result."}},"hostedApiKey":"none"},"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\\"})"}},"hostedApiKey":"none"},"agiloft_upsert_record":{"id":"agiloft_upsert_record","name":"Agiloft Upsert Record","description":"Create an Agiloft record, or update it when a record already matches the given 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., \\"contracts\\", \\"contacts.employees\\")"},"match":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field used to find an existing record (e.g., \\"ext_id\\"). Pick something that identifies a record uniquely — if more than one record matches, Agiloft writes nothing and returns a conflict."},"async":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Queue the write instead of waiting for it. Returns a callback ID instead of a record ID; pass that to Async Status to poll the result."},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field values as a JSON object. On create these populate the new record; on update only the supplied fields change."}},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"ahrefs","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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."}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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\\"."}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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)"}},"oauth":{"required":false,"provider":"algolia","credentialKind":"service-account","credentialParams":{"applicationId":"providerWorkspaceId","apiKey":"accessToken"},"credentialSuppliedParams":["applicationId","apiKey"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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":{"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"Amplitude API Key. With it, the request authenticates with both keys over Basic auth"},"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)"}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken"},"credentialSuppliedParams":["apiKey","secretKey"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"amplitude","credentialKind":"service-account","credentialParams":{"apiKey":"providerWorkspaceId","secretKey":"accessToken","dataResidency":"domain"},"credentialSuppliedParams":["apiKey","secretKey","dataResidency"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"apify","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"apify","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"apify_run_actor_async":{"id":"apify_run_actor_async","name":"APIFY Run Actor (Async)","description":"Start an APIFY Actor run and poll it within the same workflow step until completion or the polling deadline","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\\""}},"oauth":{"required":false,"provider":"apify","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"apify","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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\\""}},"oauth":{"required":false,"provider":"apify","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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."}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"apollo_contact_search":{"id":"apollo_contact_search","name":"Apollo Search Contacts","description":"Search your team\'s contacts in Apollo","version":"1.0.0","params":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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\\")"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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\\"])"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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\\")"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"apollo_task_search":{"id":"apollo_task_search","name":"Apollo Search Tasks","description":"Search for tasks in Apollo","version":"1.0.0","params":{"credentialType":{"type":"string","required":false,"visibility":"hidden","description":"Stored credential type supplied by credential resolution"},"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)"}},"oauth":{"required":false,"provider":"apollo","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"],"authoritativeParams":["credentialType"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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"}},"oauth":{"required":false,"provider":"aws","credentialKind":"service-account","credentialSuppliedParams":["accessKeyId","secretAccessKey","awsAccessKeyId","awsSecretAccessKey"]},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"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\\")"}},"hostedApiKey":"none"},"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)"}},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"hostedApiKey":"none"},"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"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"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"}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"ashby_anonymize_candidate":{"id":"ashby_anonymize_candidate","name":"Ashby Anonymize Candidate","description":"Remove personally identifiable information from an Ashby candidate while retaining the candidate record and its applications. This operation does not delete the candidate. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the candidate to anonymize"}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"ashby_change_application_source":{"id":"ashby_change_application_source","name":"Ashby Change Application Source","description":"Changes the source attributed to an existing application, so programmatically created applications report correctly on the recruiting side. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the application whose source should change"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the application to, as returned by List Sources. Omit only when unsetSource is true."},"unsetSource":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Set true to deliberately clear the application source. Required to unset, so that a missing or empty sourceId cannot wipe attribution by accident."}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"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"},"archiveEmail":{"type":"json","required":false,"visibility":"user-or-llm","description":"Archive email configuration with communicationTemplateId and optional sendAt ISO 8601 timestamp. Pass null or omit to send no archive email."}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"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, or FirstPreInterviewScreen (defaults to the 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)"},"applicationHistory":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional documented application history entries to create with the application"}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"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\\"]"},"location":{"type":"json","required":false,"visibility":"user-or-llm","description":"Candidate location object with optional city, region, and country"}},"oauth":{"required":false,"provider":"ashby","credentialKind":"service-account","credentialParams":{"apiKey":"accessToken"},"credentialSuppliedParams":["apiKey"]},"hostedApiKey":"none"},"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"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"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: , , , ,