MCP App Store
by KeenEthics
Chili Piper icon

Chili Piper

By Chili Piper

Get New App Alerts

Overview

Connect ChatGPT to your Chili Piper account to review and manage meetings, lead routing, and scheduling in plain language. Ask questions like "How many meetings did each rep get last week?", "Why didn't this lead book?", or "Why does this rep have no availability?" and get answers from your live Chili Piper data. What you can do: • Meetings: list, export, and inspect meetings, including how each one was booked and routed. Mark no-shows or cancel. • Routing: review Concierge, Distro, and Handoff routers, rules, and distributions. Audit for coverage gaps and adjust round-robin weights. • Scheduling: check rep and team availability, and manage meeting types and scheduling links. • Users and teams: look up users, manage workspace and team membership, and onboard or offboard reps. • Chat AI: review conversation logs and routing outcomes. Includes built-in skills for common admin workflows: meeting diagnostics, routing audits, no-show analysis, availability checks, and user onboarding and offboarding. Skills that make changes show you a plan and ask for confirmation first. Requires a Chili Piper account. Sign in with a Chili Piper Admin login.

Tools

asset-change-history-list

ChatGPT
The configuration change history for a workspace: an actor-attributed audit trail of create/update/delete changes to teams, rules and distributions — the same data shown in the in-product "History" tab. Use it to answer "who changed this asset, and when", e.g. who last edited a rule's conditions or changed a round-robin's weights. Coverage is teams, rules and distributions only. This is NOT the same as distribution-history-list, which is the per-assignment routing trail (who a lead was routed to / booked with); this tool is the config change log. Each entry is a flat record: eventType (e.g. RuleConditionsChanged, DistributionWeightChanged, TeamNameChanged), eventGroup (TeamEvents/RuleEvents/ DistributionEvents), actor (who made the change, as a caller ref such as user/<id>, apikey/… or job/…), entryTime, the affected entity ids (teamId/ruleId/distributionId/userId), chiliProduct, and changes (per-change detail keyed by argument name, e.g. teamName, revision). Filter with workspace (required) and the required since / optional until time window, plus optional eventGroup, eventTypes, distribution, rule, team, user, actor, and page / pageSize. actor accepts a bare user id or a caller ref. An unknown eventGroup or eventTypes value is rejected with 400 and the list of valid values. → [{entryId, entryTime, eventType, eventGroup, actor, workspaceId, teamId, ruleId, distributionId, userId, chiliProduct, changes}]

assignment-table-csv-download

ChatGPT
Downloads an assignment table's rows as CSV text — a header row plus one row per assignment-table row. If the table has no rows yet, only the header row is returned. Round-trips with assignment-table-csv-upload: download, edit, re-upload. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. → raw CSV text on HTTP and CLI (not a JSON wrapper); the MCP tool returns {csv: "<CSV text>"} see: assignment-table-csv-upload (write rows from CSV), assignment-table-get (fetch rows as JSON)

assignment-table-csv-upload

ChatGPT
Uploads a CSV to populate an assignment table's rows, validated against the persisted definition. This is a full replace of the table's rows (same semantics as assignment-table-upsert) driven by a CSV instead of a JSON body — the natural way to migrate a spreadsheet or territory export. The table's DEFINITION must already exist (assignment-table-definition-create); send definitionRevision equal to that definition's current revision so a CSV authored against a stale schema is rejected. Optimistic concurrency on the table itself uses revision: omit it on the first upload, otherwise pass the table's current revision. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - definitionRevision (req): revision of the definition the CSV was authored against. - revision (opt): the table's current revision; omit on the first upload. - csv (req): raw CSV text — a header row matching the definition's column names, then one row per assignment-table row. - columnNameOverrides (opt): map a CSV header that does not match a column name onto the column key it targets, e.g. {"Region": "territory"}; omit when the headers already match. → {id, rows, metadata: {revision, ...}} ⚠ full replace of rows; a stale table revision causes a 409 conflict — re-fetch and retry; a stale definitionRevision or rows that fail the definition checks are rejected with 400 see: assignment-table-csv-download (fetch the current CSV), assignment-table-definition-get (get the definitionRevision), assignment-table-upsert (write rows as JSON)

assignment-table-definition-create

ChatGPT
Creates the column definition (schema) for an assignment table. This must exist before any rows are written (via assignment-table-upsert). Declare the input columns (variables the rule matches on) and output columns (each fixed to a context: Record for distro, Meeting for concierge/handoff, Conversation for chat). Column keys are generated from names; reference them later when writing rows. - workspaceId (req): the workspace to create the table in. - assignmentTableId (req): the id to create the table under (path). - name (req): human-readable table name. - inputs (req): at least one input column — {name, required?, typeHint?}. typeHint is one of {type: "StringColumnType"|"NumberColumnType"|"BooleanColumnType"} or {type: "ArrayColumnType", ofType?}. - outputs (opt): output columns — {name, context, required?}; context is one of "Record"|"Meeting"|"Conversation". → {id, name, inputs, outputs, allKeys, metadata: {revision: 0, ...}} see: assignment-table-upsert (write the rows), assignment-table-definition-replace (change the schema)

assignment-table-definition-delete

ChatGPT
Deletes the column definition for an assignment table at the specified revision ONLY. Uses optimistic concurrency: pass the current revision (re-fetch via assignment-table-definition-get right before calling). Any rules that reference this table will no longer resolve an assignment. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - revision (req): the definition's current revision. → empty (204) on success ⚠ irreversible via API; a stale revision causes a 409 conflict — re-fetch and retry see: assignment-table-definition-get (get a fresh revision immediately before calling)

assignment-table-definition-get

ChatGPT
Fetches one assignment-table definition (column schema) by its assignmentTableId. Use this to read the current column layout and a fresh revision before replacing the definition or patching a column. Use assignment-table-definition-list to browse. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. → {id, name, inputs: [{key, name, required, typeHint?}], outputs: [{key, name, context, required}], allKeys, metadata: {revision, ...}} see: assignment-table-definition-list (browse), assignment-table-definition-replace (replace schema), assignment-table-get (read the rows)

assignment-table-definition-list

ChatGPT
Browses the assignment-table definitions (column schemas) in a workspace so you can discover assignmentTableIds, revisions, and column keys before reading/writing rows. An assignment-table definition describes the table's input columns (the variables a rule matches on) and output columns (each pinned to a Record/Meeting/Conversation context). Paging defaults to page 0 / pageSize 10; optionally filter by name. - workspaceId (req): the workspace whose definitions to list. - name (opt): case-sensitive filter on the definition name. → {results: [{id, name, inputs: [{key, name, required, typeHint?}], outputs: [{key, name, context, required}], allKeys, metadata: {revision, ...}}], total, page, pageSize} see: assignment-table-definition-get (fetch one), assignment-table-definition-create (add a definition)

assignment-table-definition-patch-column

ChatGPT
Renames a single column in an assignment-table definition without replacing the whole schema. Uses optimistic concurrency: pass the current revision (re-fetch via assignment-table-definition-get right before calling). The column is addressed by its stable key, so renaming does not break existing rows. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - columnKey (req): the stable key of the column to rename (from the definition's allKeys / inputs / outputs). - revision (req): the definition's current revision. - newName (opt): the new column name; omit to leave the name unchanged. → {id, name, inputs, outputs, allKeys, metadata: {revision, ...}} ⚠ a stale revision causes a 409 conflict — re-fetch and retry see: assignment-table-definition-replace (replace the whole schema)

assignment-table-definition-replace

ChatGPT
Replaces the whole column definition for an assignment table. This is a full replace of name+inputs+outputs, not a merge; column ids (keys) are preserved by name so existing rows keep matching where names are unchanged. Uses optimistic concurrency: pass the current revision (re-fetch it via assignment-table-definition-get right before calling). Removing or renaming a column can invalidate existing rows. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - revision (req): the definition's current revision (from assignment-table-definition-get). - name / inputs / outputs (req): the new schema (same shape as create). → {id, name, inputs, outputs, allKeys, metadata: {revision, ...}} ⚠ full replace; a stale revision causes a 409 conflict — re-fetch and retry see: assignment-table-definition-patch-column (rename a single column instead), assignment-table-definition-get

assignment-table-delete

ChatGPT
Deletes the rows of an assignment table at the specified revision ONLY (the DEFINITION is left intact — remove it separately via assignment-table-definition-delete). Uses optimistic concurrency: pass the table's current revision (re-fetch via assignment-table-get right before calling). - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - revision (req): the table's current revision. → empty (204) on success ⚠ irreversible via API; a stale revision causes a 409 conflict — re-fetch and retry see: assignment-table-get (get a fresh revision immediately before calling), assignment-table-definition-delete (remove the schema too)

assignment-table-get

ChatGPT
Fetches the rows (data) of a single assignment table by its id. Each row lists {key, value} pairs: input-column keys with their values, and output-column keys with a resolved assignment (a user or a distribution). Read the table's DEFINITION first (assignment-table-definition-get) to learn the column keys. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. → {id, rows: [{name?, inputs: [{key: <input-column key>, value: <JSON value>}], outputs: [{key: <output-column key>, value: {tableOutType, value, humanReadableName?, context?}}]}], metadata: {revision, ...}} see: assignment-table-get-by-ids (fetch several at once), assignment-table-upsert (write rows), assignment-table-definition-get (read the schema)

assignment-table-get-by-ids

ChatGPT
Fetches the rows of several assignment tables in one call. Ids without a stored table are omitted from the response (no error). Useful to hydrate the tables referenced by a set of assignment rules. - workspaceId (req): the workspace that owns the tables. - ids (req): one or more assignmentTableIds. → {<assignmentTableId>: {id, rows, metadata}} Note: ids with no stored table are omitted from the map. see: assignment-table-get (fetch a single table)

assignment-table-upsert

ChatGPT
Creates or replaces ALL rows of an assignment table, validating them against the persisted definition. This is a full replace of the table's rows, not an append. The table's DEFINITION must already exist (assignment-table-definition-create) — send definitionRevision equal to that definition's current revision so a write built against a stale schema is rejected. Optimistic concurrency on the table itself uses revision: omit it on the first upsert, otherwise pass the table's current revision. - workspaceId (req): the workspace that owns the table. - assignmentTableId (req): the table's id. - revision (opt): the table's current revision; omit on the first upsert. - table (req): {rows: [{name?, inputs, outputs}]} — at least one row. inputs is a list of [{key: <input-column key>, value: <JSON value>}]; outputs is a list of [{key: <output-column key>, value: {tableOutType: "User"|"Distribution", value: {type: "UserAssignment", userId} | {type: "DistributionAssignment", distributionId}, humanReadableName?, context?}}]. - definitionRevision (req): revision of the definition the rows were authored against. → {id, rows, metadata: {revision, ...}} ⚠ full replace of rows; a stale table revision causes a 409 conflict — re-fetch and retry; a stale definitionRevision or rows that fail the definition checks are rejected with 400 see: assignment-table-definition-get (get the definitionRevision), assignment-table-get (get the table revision)

availability-configured

ChatGPT
Batch, side-effect-free check of whether each user has configured their availability. A user is "configured" when they have at least one custom schedule, or their default schedule's working hours / timezone differ from the bootstrap default (9-5, timezone synced from calendar). Users with no persisted schedule (and users not present in the request) are returned as false; reading does not create a default schedule for users that lack one. - userIds: the users to check. → [{userId, availabilityConfigured}] see: user-find (resolve userIds), availability-slots-v2

availability-slots-v2

ChatGPT
Returns bookable start times for a meeting type over a time window, one page at a time, so a wide window never produces a single oversized response. Prefer this over the deprecated availability-slots (which had the same attendee model but no paging); page through wide windows rather than narrowing the interval. - attendees: non-empty list of who joins the meeting, discriminated by type: {type: "ManuallyAssigned", userId, required: true|false} {type: "DistributionAssignee", distributionId, required: true|false, userIds (opt): ranked shortlist} {type: "AssignedViaTeam", userId, teamId, required: true|false} {type: "AdditionalAttendee", userId, required: true|false} Note: each attendee's required flag is mandatory (omitting it returns 400) and gates the slot — true hides slots where that attendee is busy. - expectedHost: {type: "User", userId} or {type: "AssigneeFromDistribution", distributionId}. - meetingTypeOverride (opt): inline duration/buffer overrides; omit or pass null to use meeting-type defaults. - interval: {startsAt: ISO-8601, duration: "7 days"/"P7D"/etc.}. - pagination (opt): {page (0-indexed, default 0), pageSize (default 100, max 500)}. → {results: [{startTime, attendees}], total, page, pageSize} Note: slots are ordered by startTime; keep requesting page+1 until you have collected total slots. see: distribution-list, user-find

availability-toggle-get

ChatGPT
Batch, side-effect-free read of each user's Available/Away (online/offline) toggle. Concierge Live and Handoff Live use this toggle: Away users are not offered for live routing. - userIds: non-empty list of the users to read. → [{userId, status}] Note: one entry per distinct requested userId. status is Available, Away, or NotSet (the user never set the toggle). see: availability-toggle-set, user-find (resolve userIds)

availability-toggle-set

ChatGPT
Sets one user's Available/Away (online/offline) toggle. Concierge Live and Handoff Live use this toggle: Away users are not offered for live routing; Available users are. Setting the current value again is a no-op. - userId: the user to update (use user-find first). - status: Available or Away. → {userId, status} see: availability-toggle-get (verify), user-find (resolve userId)

campaign-list

ChatGPT
Lists Salesforce campaigns for the tenant's connected org so you can find the campaignId used by a router's "Add to Campaign" CRM action. Salesforce-only. Use this to browse/paginate the full set; for large orgs prefer campaign-search. - isActive (opt): filter to active (true) or inactive (false) campaigns; omit for all - page (opt, default 0), pageSize (opt, default 10) → {results: [{id, name, status, startDate, endDate, active}], total, page, pageSize} Note: id is the Salesforce campaign id to drop into the Add-to-Campaign action's campaignId ⚠ Salesforce paginates via SOQL OFFSET, capped at 2000 rows — beyond that use campaign-search ⚠ 4xx if Salesforce is not connected for the tenant see: campaign-search (full-text search, preferred for large orgs)

chat-logs

ChatGPT
Reads a workspace's Chat AI conversation logs over a time window — the read-only audit trail of who chatted, how they were routed, and what got booked. Each entry carries the full bot/guest transcript, the routing outcome, and any meetings booked. Use it to inspect or debug live chat routing after the fact. The start/end window (ISO-8601) spans at most 30 days. Pass playbookId (repeatable) to restrict to specific chat playbooks. Narrow to a specific guest or rule with guestEmail (case-insensitive exact match), guestId (exact match), ruleId (exact match on any routing rule executed in the session; stable across rule renames), and/or ruleName (exact match on any routing rule executed in the session). Pages start at 0; pageSize defaults to 10, max 50. → {results: [{guestId, sessionId, playbookId, guestEmail, startedAt, endedAt, ended, targetedUrl, respondedUrl, routingOutcome, ruleId, ruleName, conversationAssigneeId, routedAt, repJoined, chatAiStarted, meetingBooked, meetings: [{assigneeId, origin, scheduledAt}], messages: [{role, content, timestamp}]}], total, page, pageSize} Note: routingOutcome is one of Routed | NotRouted | Abandoned; role is one of Bot | Guest; meetings[N].origin is one of Journey | Inbox | NoMeeting see: workspace-list (find workspaceId)

chat-transcript

ChatGPT
Reads the conversation transcript for a single guest — every message actually sent, drawn from the message archive rather than the session log. Unlike chat-logs (whose messages carry only Chat AI turns), the transcript spans all three senders: Guest, Bot and Rep (a rep who took over the live chat). A reply-button selection reads as its label text ("<label> [Shortcut]") attributed to the Guest. Pages are cursor-paginated newest-first: omit before for the latest page, then echo the returned nextCursor to walk older pages — pass nextCursor.before as before and each nextCursor.seenIds value as a beforeSeenId; nextCursor is absent at the room start. pageSize defaults to 50 (max 200). Find the guestId via chat-logs over the workspace and time window. → {messages: [{sender, text, timestamp}], nextCursor: {before, seenIds}} Note: sender is one of Guest | Bot | Rep; nextCursor is the cursor for the next-older page (echo before + seenIds), absent at the room start see: chat-logs (find guestId), workspace-list (find workspaceId)

concierge-call-logs

ChatGPT
Lists a Concierge router's phone-call flows over a time window — each flow is one inbound routing that dialed one or more reps, with the per-rep call legs nested underneath. start/end are ISO-8601 and the window may span at most 30 days. Newest flows first. → [{flowId, routingId, routerId, routerName, totalCallDuration, createdAt, updatedAt, calls: [{userId, status, twilioCallSid, createdAt, updatedAt}]}] Note: status is one of MissingPhoneNumber, Queued, Ringing, InProgress, Completed, Busy, Failed, NoAnswer, Canceled see: concierge-list-routers (find routerId+workspaceId)

concierge-folder-create

ChatGPT
Creates a concierge router folder (asset group) in the workspace. The folder starts empty; assign concierge router items to it with concierge-folder-move / concierge-folder-move-bulk. → {id, name, displayOrder, itemCount: 0, revision} see: concierge-folder-move (assign a concierge router to the new folder), concierge-folder-list (list folders)

concierge-folder-delete

ChatGPT
Deletes a concierge router folder. Member concierge router items are not deleted — they are detached (moved to ungrouped). Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via concierge-folder-list right before calling). → no payload on success ⚠ revision conflict = re-fetch via concierge-folder-list and retry see: concierge-folder-list (get a fresh folderId+revision immediately before calling)

concierge-folder-list

ChatGPT
Lists the concierge router folders (asset groups) in a workspace, ordered by display order. Use this to discover folderIds and grab a fresh revision before renaming or deleting a folder. → {items: [{id, name, displayOrder, itemCount, revision}]} ⚠ call immediately before concierge-folder-rename / concierge-folder-delete to get a fresh revision — a stale revision causes a conflict error see: concierge-folder-create (add a folder), concierge-folder-move (assign a concierge router to a folder)

concierge-folder-move

ChatGPT
Assigns one concierge router to a folder, or clears its folder membership when folderId is omitted (null) — last-write-wins. → no payload on success see: concierge-folder-list (find a folderId), concierge-folder-move-bulk (assign many concierge router items at once)

concierge-folder-move-bulk

ChatGPT
Assigns multiple concierge router items to a folder, or clears their folder membership when folderId is omitted (null), in one request — last-write-wins. → no payload on success see: concierge-folder-list (find a folderId), concierge-folder-move (assign a single concierge router)

concierge-folder-rename

ChatGPT
Renames a concierge router folder. Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via concierge-folder-list right before calling). → {id, name, displayOrder, itemCount, revision} ⚠ revision conflict = re-fetch via concierge-folder-list and retry see: concierge-folder-list (get a fresh folderId+revision immediately before calling)

concierge-list-routers

ChatGPT
Browses Concierge routers to discover routerIds and slugs. A Concierge router evaluates inbound lead data and routes it to teams/users via rules. Pass workspaceId to restrict to one workspace; omit it for the whole org. → {routers: [{router: {id (routerId), name, slug, formMapping, settings, routing, meta}, dataFields: [...], workspaceId}]} Note: routerId is at routers[N].router.id; slug is at routers[N].router.slug see: concierge-logs (audit decisions, needs routerId+workspaceId), concierge-route-by-slug (execute routing, needs slug)

concierge-logs

ChatGPT
Audits a Concierge router's routing decisions over a time window — who got routed where and whether actions succeeded. start/end are ISO-8601 and the window may span at most 30 days. → array of log entries, each with fields: id, status, trigger, assignments, triggeredAt, crmUrl, guestEmail, actionsStatus, sourceUrl, meetingId, routing, matchedPath (payload structure may change) see: concierge-list-routers (find routerId+workspaceId)

concierge-route

ChatGPT
Runs a Concierge router without an explicit slug — the router is resolved from body context (options.router is required). Behaviorally identical to concierge-route-by-slug once resolved; prefer concierge-route-by-slug when you know the slug, since ambiguous resolution can error or route to the wrong router. Pass an interval to also get available startTimes for booking via concierge-schedule. - body (req): RoutingRequestBody; options.router (router slug) required for resolution; same options as concierge-route-by-slug - interval (opt): {startsAt, duration} — when provided, the response includes available startTimes for scheduling via concierge-schedule - meetingTypeOverride (opt): per-booking length override {meetingDurationOverride, beforeBufferDurationOverride, afterBufferDurationOverride, availabilityIncrementOverride}, each a duration string like "15 minutes"; slots and the booking (concierge-schedule) use the overridden length, bounds validated by the availability service → {routeId, routingLink, distributionIds, assignments, schedulingAllowed, timeoutInMS, timeoutRedirectUrl; when interval given and schedulingAllowed: schedulingData with meetingTypeRef, duration, locations, startTimes, failures} Note: use routeId as the {routingId} path parameter in concierge-schedule ⚠ routing session has TTL; if router resolution is ambiguous the call errors or routes to wrong router — prefer concierge-route-by-slug when slug is known see: concierge-list-routers (find slug), concierge-logs (audit after), concierge-schedule (book a slot)

concierge-route-by-slug

ChatGPT
Runs a Concierge router (identified by slug) against a lead: evaluates the rules, creates a routing session, and returns a booking link to deliver to the lead. This is the slug-based entry point; use concierge-route when you only have body context. Pass an interval to also get available startTimes, then book one via concierge-schedule using the returned routeId. - body (req): RoutingRequestBody with lead data; key options fields: - options.id: prospect email/CRM ID - options.assignee: user slug (force assignment) - options.assigneeCrmId, accountId, opportunityId, caseId, objectId: Salesforce IDs - options.timezone: e.g. "America/New_York" - options.by: booker user slug - form: map of field key→value for form-based rules - interval (opt): {startsAt, duration} — when provided, the response includes available startTimes for scheduling via concierge-schedule - meetingTypeOverride (opt): per-booking length override {meetingDurationOverride, beforeBufferDurationOverride, afterBufferDurationOverride, availabilityIncrementOverride}, each a duration string like "15 minutes"; slots and the booking (concierge-schedule) use the overridden length, bounds validated by the availability service → {routeId, routingLink, distributionIds, assignments, schedulingAllowed, timeoutInMS, timeoutRedirectUrl; when interval given and schedulingAllowed: schedulingData with meetingTypeRef, duration, locations, startTimes, failures} Note: use routeId as the {routingId} path parameter in concierge-schedule ⚠ routing session has a TTL — deliver routingLink to lead promptly; no meeting booked until lead completes scheduling see: concierge-list-routers (find slug), concierge-logs (audit after), concierge-route (slugless variant), concierge-schedule (book a slot using routeId)

concierge-router-create

ChatGPT
Creates a Concierge router and publishes it live in one step — there is no unpublished-draft state via the API. workspaceId must be a team workspace of this tenant (400 otherwise). The URL slug is derived from name on publish (there is no separate slug field). Triggers are a PRODUCT of optional kinds — set a webform (either form for a Chili-hosted form OR thirdPartyForm for an external form — mutually exclusive), inAppButton, and routerLink in any combination; supplying NONE auto-generates a minimal email-only Chili webform. A routerLink is what gives the router a shareable Router Link URL. branding and localizations are optional. Creating and publishing a router does NOT require a legacy per-user Concierge product licence — Concierge access is governed by the tenant's GBB tier (e.g. the Experiences tier includes it); to check a caller's entitlement read their tier and concierge/conciergeLive flags via user-read or user-find. - routing (req): ordered rules evaluated top-down, plus an optional catch-all fallback. Each row's rule, when it matches, runs that row's outcome; the catch-all outcome runs when no row matches: {routes: [{ruleId, outcome}], catchAll?: outcome} outcome (one of): {type: "Schedule", assignment, meetingTypeId, additionalMeetingTypeIds?, timeout?, crmActions?, redirectAfterBooking?} {type: "Redirect", url} Schedule assigns the lead and books a meeting type. assignment is {type: "Distribution", distributionId} (round-robin / ownership / whatever the distribution does) or {type: "User", userId} (a specific host). meetingTypeId is the meeting type booked on this path; additionalMeetingTypeIds (opt) offers further meeting types on the same path (a group of options the guest picks from) — the booked set is meetingTypeId followed by them, in order. timeout (opt) {minutes, onTimeout} sets the no-show fallback; onTimeout is {type: "Landing"} or {type: "Url", url}; omit timeout for the default (10 min → Landing). crmActions (opt) is an ordered post-booking chain (ConvertLead is rejected with 400 — Concierge does not execute it; use a Distro router's convertLead), each {type: "AddToCampaign", campaignId, memberStatus} (add the matched Salesforce lead/contact to a campaign with that member status; find campaignId with campaign-list / campaign-search), {type: "SalesforceUpdateFields", contact:[{object,field,value}], lead:[{field,value}]} (write field values onto the matched Salesforce contact/lead), {type: "HubspotUpdateFields", contact:[{object,field,value}]} (write property values onto the matched HubSpot record), {type: "SalesforceUpsertRecord", settings:{noMatch,whenMatch}} (create/update the matched Salesforce lead/contact), or {type: "HubspotUpsertRecord", settings:{noMatch,whenMatch}} (create/update the matched HubSpot contact), {type: "SalesforceUpdateOwnership", contact:[{object,field}], lead:[{field}]} (set the record owner to the booked host), or {type: "HubspotUpdateOwnership", contact:[{object,field}]} (set the record owner to the booked host), {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?} (create a Salesforce Event for the booked meeting; relatedTo links it to a CRM object — {type: "Account"|"Opportunity"|"Case"}, {type: "Campaign", campaignId}, {type: "ExplicitObject", id}, {type: "NoRelationNeeded"} or {type: "RelationDisabled"} — omit for no relation; meetingCancellationBehavior DeleteEvent|DoNothing; guestsBehavior CreateEvents|DoNothing), or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?} (create a HubSpot meeting engagement; relatedTo {type: "Company"|"Deal"|"Ticket"}; owner Assignee|Booker). At most one action of each type per chain — a duplicated type is rejected (400); put all rows of one kind in a single action. A Slack Notify cannot be written here: the Concierge backend does not allow it after a booking (it may still appear on reads). redirectAfterBooking (opt) {url, forwardFormDataAsQueryParams?, delaySeconds?} sends the visitor to url after they book (the app's "Redirect To" step on the Scheduled path); …

concierge-router-delete

ChatGPT
Deletes a Concierge router by id. - routerId (req): the router's id (path) ⚠ irreversible via API; any embeds or links pointing at this router stop working see: concierge-list-routers or concierge-router-get (confirm the id before deleting)

concierge-router-get

ChatGPT
Fetches one Concierge router: its identity, a lossy per-row summary of its routing, and its full-config dimensions (guest form, branding/cover, localizations). Call this before concierge-router-update to check routing.representable (whether a routing update full-replaces or overlays) and to see whether the webform is a Chili form (form) or a third-party form (thirdPartyForm). → {id, workspaceId, name?, slug?, routing: {known, representable, rows: [{ruleId?, ruleType?, outcome}], catchAll?: {outcome}}, form?, thirdPartyForm?, inAppButton?, routerLink?, branding?, localizations?, routingSteps: [step]} outcome (one of): {type: "Schedule", distributionId?, userId?, additionalAssignments?, meetingTypeId?, additionalMeetingTypeIds?, timeout?, crmActions?, redirectAfterBooking?} | {type: "Redirect", url?} | {type: "OwnerAssign"} | {type: "ContactOptions", meeting?, callSuccess?, callMissed?, meetingTypeIds?, timeoutMinutes?, meetingTimeout?, callMissedTimeout?} | {type: "CrmAction"} | {type: "Other", kind} form: {representable, fields: [{dataField, label, description?, required, hidden?}], readOnlyTriggers: [string]} thirdPartyForm: {fields: [{formFieldName, dataField, label?}]} inAppButton: {fields: [{dataField}]} routerLink: {fields: [{dataField, label, required, hidden?}]} branding: {coverImage?, headingText?, language?} localizations: {"<lang-tag>": {"<key>": "<text>"}} step (one of): {type: "Enrichment", waterfalls: [{dataField, waterfallId}], timeoutSeconds?} | {type: "SpamCheck", writeSpamScoreToDataField, onSpam} | {type: "Unrepresentable", kind} Note: on a Schedule, crmActions is the readable post-booking chain — each {type: "ConvertLead"} (stored on older routers but NOT executed by Concierge), {type: "Notify", slackChannel?} (read-only; cannot be written), {type: "AddToCampaign", campaignId, memberStatus}, {type: "SalesforceUpdateFields", contact, lead}, {type: "HubspotUpdateFields", contact}, {type: "SalesforceUpsertRecord", settings}, {type: "HubspotUpsertRecord", settings}, {type: "SalesforceUpdateOwnership", contact, lead}, {type: "HubspotUpdateOwnership", contact}, {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?}, or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?}; a node Edge can't model appears as {type: "Other", kind} (and representable is false) instead of nulling the whole chain Note: on a Schedule, redirectAfterBooking {url, forwardFormDataAsQueryParams?, delaySeconds?} is the post-booking "Redirect To" step (where the visitor goes after booking); a redirect Edge can't model stays in crmActions as {type: "Other", kind} Note: on a Schedule, timeout is the no-show fallback {minutes, onTimeout?} ("wait for prospect" on the Display Calendar node), null when the path has none; onTimeout is null when the redirect has settings Edge can't express (a minutes-only update keeps it). distributionId / userId (or ruleId + columnKey) is the path's FIRST assignee; an app-built path can book several — additionalAssignments lists the rest, in order ({type: "Distribution", distributionId} | {type: "User", userId} | {type: "UseAssignmentTable", ruleId, columnKey}); concierge-router-update never drops them Note: a Schedule path may offer more than one meeting type — meetingTypeId is the first and additionalMeetingTypeIds (when present) holds the rest, in order; concierge-router-update round-trips the whole set (send them back verbatim to keep every option) Note: a ContactOptions (call-flow) outcome carries the post-booking crmActions chains of its branches — meeting (book directly), callSuccess (after a connected call), callMissed (no answer) — each the same action list as a Schedule's crmActions (null when that branch is absent). timeoutMinutes is how long the options page waits for the prospect to choose; meetingTimeout / callMissedTimeout ({minutes, onTimeout?}) are the Display Calendar no-show timeouts of the book-a-meeting branch / the call-missed "show calendar" branch — these three are editable via concierge-router-update (a ContactOptions pat…

concierge-router-update

ChatGPT
Edits a Concierge router and republishes it live. Only the fields you supply change; omitted fields (and config dimensions Edge doesn't model, e.g. router-link enrichment waterfalls and CRM-upsert settings) are preserved. Supplying name re-derives the URL slug. Each dimension you send is a full replace, except branding which merges per sub-field and routing on an app-built router which is overlaid (see below). Triggers are a product of optional kinds: a webform (form OR thirdPartyForm — mutually exclusive), inAppButton, and routerLink each replace only their own kind — writing one never destroys the others. Call concierge-router-get first to inspect the current config. - routing (opt): when present, sets the routing matrix; when omitted, the current routing is kept. Each row's rule, when it matches, runs that row's outcome; the catch-all outcome runs when no row matches. Each outcome is a PATCH of the existing path with the same ruleId (or of the catch-all): a Schedule field you omit keeps that path's current value, so e.g. {type: "Schedule", timeout: {minutes: 8, onTimeout: {type: "Landing"}}} changes only the no-show timeout ("wait for prospect"). A row with no existing Schedule path to patch (new ruleId, or a path that is currently another outcome) needs assignment and meetingTypeId: {routes: [{ruleId, outcome}], catchAll?: outcome} outcome (one of): {type: "Schedule", assignment?, meetingTypeId?, additionalMeetingTypeIds?, timeout?: {minutes?, onTimeout?}, crmActions?, redirectAfterBooking?} {type: "ContactOptions", timeoutMinutes?, meetingTimeout?: {minutes?, onTimeout?}, callMissedTimeout?: {minutes?, onTimeout?}} {type: "Redirect", url} Schedule assigns the lead and books a meeting type. assignment is {type: "Distribution", distributionId} or {type: "User", userId}. meetingTypeId is the meeting type booked on this path; additionalMeetingTypeIds (opt) offers further meeting types on the same path (a group of options). timeout (opt) {minutes, onTimeout} sets the no-show fallback; onTimeout is {type: "Landing"} or {type: "Url", url}; omit timeout (or either of its fields) to keep the path's current value — {minutes: 8} changes only the wait and keeps the redirect (a newly built path defaults to 10 min → Landing). crmActions (opt) is an ordered post-booking chain, each {type: "ConvertLead"} (kept only on a route that already has it — Concierge does not execute it, so adding it is rejected with 400; use a Distro router's convertLead), {type: "AddToCampaign", campaignId, memberStatus} (add the matched Salesforce lead/contact to a campaign with that member status; find campaignId with campaign-list / campaign-search), {type: "SalesforceUpdateFields", contact:[{object,field,value}], lead:[{field,value}]} (write field values onto the matched Salesforce contact/lead), {type: "HubspotUpdateFields", contact:[{object,field,value}]} (write property values onto the matched HubSpot record), {type: "SalesforceUpsertRecord", settings:{noMatch,whenMatch}} (create/update the matched Salesforce lead/contact), or {type: "HubspotUpsertRecord", settings:{noMatch,whenMatch}} (create/update the matched HubSpot contact), {type: "SalesforceUpdateOwnership", contact:[{object,field}], lead:[{field}]} (set the record owner to the booked host), or {type: "HubspotUpdateOwnership", contact:[{object,field}]} (set the record owner to the booked host), {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?} (create a Salesforce Event for the booked meeting; relatedTo links it to a CRM object — {type: "Account"|"Opportunity"|"Case"}, {type: "Campaign", campaignId}, {type: "ExplicitObject", id}, {type: "NoRelationNeeded"} or {type: "RelationDisabled"} — omit for no relation; meetingCancellationBehavior DeleteEvent|DoNothing; guestsBehavior CreateEvents|DoNothing), or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?} (create a HubSpot meeting engagement; relatedTo {type: "Company"|"Deal"|"Ticket"}; owner Assignee|Booker). At most one action of each type per chain — a duplicated typ…

concierge-schedule

ChatGPT
Books a meeting for a lead at one of the startTimes returned by a prior concierge-route-by-slug / concierge-route call — which must have been run with an interval. Pass that call's routeId as routingId. - body (req): startTime (ISO-8601 from startTimes in routing response), optional location override, optional additionalGuests — array of {email, required?} (required defaults to true; set false to invite as an optional attendee), each added to the calendar invite as an external attendee Note: additionalGuests are invited by email only — their calendar availability is NOT checked (only the routed assignees' is). → {meetingId} ⚠ routing session expires after TTL — call promptly after routing see: concierge-route-by-slug (run routing with interval to get routeId + startTimes)

crm-activity

ChatGPT
Returns the admin-UI deep link for the Chili Piper meeting linked to a CRM event — the CRM-keyed twin of meeting-activity, for jumping from a Salesforce record straight to the meeting's activity page. Accepts a 15- or 18-char Salesforce EventId or equivalent. → {url: "https://{subdomain}.chilipiper.{tld}/fire/admin/workspaces/{workspaceId}/reporting/meetings-activity/{meetingId}"} see: meeting-activity (if CP meetingId already known)

crm-cancel-post

ChatGPT
Cancels the Chili Piper meeting linked to a CRM event (v2 POST variant) — the CRM-keyed twin of meeting-cancel-post. Resolves the CRM id (15- or 18-char Salesforce EventId or equivalent) to its meeting, cancels it, and returns the updated meeting record. → {meetingId, meetingStatus: "CANCELLED", ...} ⚠ irreversible; may email attendees; 404 if the CRM id has no linked meeting see: crm-get (verify before/after), meeting-cancel-post (if CP meetingId already known), crm-cancel (v1 GET variant)

crm-get

ChatGPT
Looks up the Chili Piper meeting linked to a CRM event and returns its full record — the CRM-keyed twin of meeting-get, handy when you only hold a Salesforce id. Accepts a 15- or 18-char Salesforce EventId or equivalent; the returned linkedCrmEventIds shows every CRM record tied to the meeting. → {meetingId, status, attendees, assignedUserId, workspaceId, scheduledAt, createdAt, linkedCrmEventIds} see: crm-cancel/noshow (change status), crm-activity (admin URL), meeting-get (if CP meetingId already known)

crm-noshow-post

ChatGPT
Marks the Chili Piper meeting linked to a CRM event as no-show (v2 POST variant) — the CRM-keyed twin of meeting-noshow-post. Resolves the CRM id (15- or 18-char Salesforce EventId or equivalent) to its meeting, marks it as no-show, and returns the updated meeting record. → {meetingId, meetingStatus: "NO_SHOW", ...} ⚠ not reversible via API (admin UI only); may trigger CRM/notification workflows; 404 if the CRM id has no linked meeting see: crm-get (verify before/after), meeting-noshow-post (if CP meetingId already known), crm-noshow (v1 GET variant)

data-field-create

ChatGPT
Creates a new custom data field and publishes it in a single call — the underlying draft/publish steps are handled internally. Request body: {label, description?, objectType: "Person"|"Company"|"DeanonymizedCompany"|"DeanonymizedPerson", dataType, mappings?: [...], smartParameters?: ["email", ...]} → {reference, objectType, label, dataType, mappings: [...], smartParameters: [...]} Note: dataType list variants (Picklist/RadioButton/Checkbox) require a non-empty values list. smartParameters are lowercase slugs, each unique across the tenant's fields. see: data-field-update (edit it), data-field-set-smart-parameters (change aliases), data-field-list (find references)

data-field-delete

ChatGPT
Deletes a custom data field by reference. Returns 204 No Content on success. Only custom fields can be deleted — default/internal references are rejected. → {} ⚠ irreversible via API see: data-field-get (confirm the field before deleting)

data-field-get

ChatGPT
Fetches one data field by its reference (a custom field's UUID, or a default/internal field's stable name). → {reference, objectType, label, dataType, mappings: [...], smartParameters: [...]} see: data-field-list (find references)

data-field-list

ChatGPT
Lists the tenant's data fields — custom, default and internal — each with its reference, object type, label, value type and per-CRM mappings. With no filter, returns every field. Optional filters (combined with AND; values in one list combine with OR): name (case-insensitive substring of the label), references, objectTypes, dataTypes (the dataType type, e.g. Picklist, Email). → [{reference, objectType: "Person"|"Company"|"DeanonymizedCompany"|"DeanonymizedPerson", label, dataType, mappings: [...], smartParameters: [...]}] Note: reference is a UUID for custom fields, or a stable name for default/internal fields. Only custom fields can be created, updated or deleted; smart parameters can be set on any field. see: data-field-get (one by reference), data-field-create (add a custom field)

data-field-set-smart-parameters

ChatGPT
Replaces the smart parameters (URL-parameter aliases) of a data field, identified by reference. Smart parameters are the lowercase slugs a booking/concierge link can carry as ?<alias>=<value> to pre-fill the field. Send the complete desired set; the previous set is replaced. Works for custom, default AND internal fields (unlike update, which is custom-only). Request body: {smartParameters: ["email", "linkedin_email"]} → {reference, objectType, label, dataType, mappings: [...], smartParameters: [...]} Note: each value must be a lowercase slug matching [-0-9a-z_]+ and unique across the tenant's data fields; a duplicate is rejected. Send an empty list to clear all. see: data-field-get (inspect current smart parameters)

data-field-update

ChatGPT
Patches a custom data field, then republishes it. Every field is optional; send only what you want to change and omitted fields keep their current value. Only custom fields can be updated. {label?, description?, objectType?, dataType?, mappings?: [...]} → {reference, objectType, label, dataType, mappings: [...], smartParameters: [...]} Note: this patch does not touch smart parameters; use data-field-set-smart-parameters to change them. see: data-field-get (check the current field before patching), data-field-set-smart-parameters (change aliases)

distribution-adjust-v3

ChatGPT
Nudges a distribution and publishes immediately, MERGING rather than replacing — the preferred adjust path (v1 distribution-adjust lacks assignmentTypeConfig and teamId). weights and manualCalibration are per-userId DELTAS added to current values; assignees you omit are untouched. Use this to tweak a live distribution without restating its whole config; use distribution-update-v3 to overwrite. resetDistribution (default false) zeroes counters first; teamId moves the distribution to another team, effective immediately. - assignmentTypeConfig (opt): discriminated by type (see distribution-update-v3 for full structure): {type: "Record", handling: {type: "WorkingHours"}|{type: "Strict"}} {type: "Meeting", handling: {type: "Flexible"|"Strict", ...}, limits: {type: "MeetingLimitUnset"}|{type: "MeetingLimitSet", ...}} {type: "Conversation"} → {} ⚠ live immediately see: distribution-list (get distributionId and verify after), team-list (find teamId), distribution-update-v3 (assignmentTypeConfig full structure)

distribution-create

ChatGPT
Creates and immediately publishes a distribution — a reusable assignment pool that routing rules and round-robin scheduling links reference. name is required: a nameless distribution is a legacy (v1) one that never appears in distribution-list and cannot be used until named. assignmentTypeConfig fixes what it distributes (records, meetings, or conversations). Tune weights/caps afterwards with distribution-update-v3 or distribution-adjust-v3. - assignmentTypeConfig (req): discriminated by type: {type: "Record", handling: {type: "WorkingHours"}|{type: "Strict"}} {type: "Meeting", handling: {type: "Flexible", reassignmentType: "SameUser"|"AnyTeamMember", allowPickingAssignee: bool} |{type: "Strict", hideAssignee: bool}, limits: {type: "MeetingLimitUnset"} |{type: "MeetingLimitSet", userCaps: [{userId, limit}], resetPeriodicity: <meeting-limit reset>}} {type: "Conversation"} - weights (opt): [{userId, weight}]; manualCalibration (opt): [{userId, count}] - capping (opt): {userCaps: [{userId, limit}], resetPeriodicity: <assignment-cap reset>} - reset shapes: two different models are both named Daily/Weekly/Monthly; do not mix their fields: meeting-limit reset — only for limits.resetPeriodicity of a Meeting config (same shape as availability meeting limits): {type: "Daily", every, startsOn (YYYY-MM-DD), resetLocalTime (HH:MM), timezone} {type: "Weekly", every, startsOn, resetDayOfWeek ("MONDAY".."SUNDAY"), timezone} {type: "Monthly", every, startsOn, resetDay (1-28), timezone} assignment-cap reset — only for capping.resetPeriodicity: {type: "HourlyRolling"}|{type: "Daily", timeZone}|{type: "Weekly", timeZone, dayOfWeek (1-7)}|{type: "Monthly", timeZone} Note: every = reset every N days/weeks/months, counted from startsOn. Casing differs: timezone (meeting limit) vs timeZone (assignment cap). → {distributionId} see: team-list (find teamId), distribution-list (verify after)

distribution-delete

ChatGPT
Permanently (hard) deletes a distribution. Uses HTTP DELETE on the same path as distribution-update (DELETE /v1/org/distribution/{distributionId}) — there is no separate /delete path. Deletion does NOT cascade: fix referencing routing rules and round-robin scheduling links yourself first, or they dangle. → {} ⚠ irreversible; routing rules referencing this distributionId become dangling — audit and update/delete them first; round-robin scheduling links pointing to it become broken — no cascade cleanup see: distribution-list (confirm id), rule-list (find referencing rules to fix first), scheduling-link-list-round-robin (find affected links)

distribution-history-list

ChatGPT
The Distribution History assignment trail for a workspace: one entry per routed record or booked meeting, the same data shown in the in-product Distribution History view. Use it to audit round-robin fairness — e.g. how often the next-up rep is skipped when a booker manually picks a different rep. Each entry is a flat record. For skip analysis compare these three: - distributionUserId — the rep the distribution/round-robin selected (the "next up" rep). - hostId — who actually got the meeting (the host). - bookerId — who booked the meeting. A booker picking someone other than the next-up rep is a skip. Other fields: assignmentType (Meeting/Record), origin (the product feature, e.g. DistroRouting, HandoffRouting, ConciergeRouting, RoundRobinLink — same values the origin filter accepts), status, eventTypes, routerId, teamId, distributionId, updatedBy (the caller who made the change), recordName/recordUrl (records only), timestamp. Filter with workspace (required), assignmentType (Meeting/Record), product, origin, router, route, rule, ruleCategory, team, user, distribution, notNull, eventTypes, plus the required since and optional until time window. Results are ordered by timestamp. ⚠ assignmentType and origin are matched case-insensitively against the known values; an unrecognised value (e.g. a typo) is ignored rather than rejected, which widens the results — check spelling if you get more than you expect. → [{assignmentId, assignmentType, origin, workspaceId, timestamp, status, eventTypes, routerId, teamId, distributionId, distributionUserId, hostId, bookerId, updatedBy, recordName, recordUrl}]

distribution-list-put

ChatGPT
Browses published distributions in the org — the preferred, non-deprecated list. Filters go in the request body (avoiding query-string length limits): workspaceIds, name (substring match), and assignmentType ("Record" | "Meeting" | "Conversation"). Paginated: HTTP default pageSize 10; MCP returns up to 200 when pagination is omitted. → {results: [{id, published: {distributionId, workspaceId, name, teamRef: {id, revision}, weights, manualCalibration, assignmentTypeConfig, capping, metadata}, state: {userStates, lastReset}, prioritization: {queue, onVacation, reachedCap}}], total, page, pageSize} Note: total is the full count across all pages (use it to paginate); page/pageSize echo the request. see: distribution-list (GET equivalent)

distribution-update-v3

ChatGPT
Overwrites a distribution's full config and publishes immediately — the preferred update path (v1 distribution-update lacks assignmentTypeConfig, capping, and teamId). Full REPLACE, not a merge: weights (userId→weight), manualCalibration, and capping each supersede all existing values, so omitted assignees drop to weight 0 — send the complete desired maps. resetDistribution (default false) zeroes counters first; teamId moves the distribution to another team, effective immediately. - assignmentTypeConfig (opt): discriminated by type: {type: "Record", handling: {type: "WorkingHours"}|{type: "Strict"}} {type: "Meeting", handling: {type: "Flexible", reassignmentType: "SameUser"|"AnyTeamMember", allowPickingAssignee: bool} |{type: "Strict", hideAssignee: bool}, limits: {type: "MeetingLimitUnset"} |{type: "MeetingLimitSet", userCaps: [{userId, limit}], resetPeriodicity: <meeting-limit reset>}} {type: "Conversation"} - capping (opt): {userCaps: [{userId, limit}], resetPeriodicity: <assignment-cap reset>} - reset shapes: two different models are both named Daily/Weekly/Monthly; do not mix their fields: meeting-limit reset — only for limits.resetPeriodicity of a Meeting config (same shape as availability meeting limits): {type: "Daily", every, startsOn (YYYY-MM-DD), resetLocalTime (HH:MM), timezone} {type: "Weekly", every, startsOn, resetDayOfWeek ("MONDAY".."SUNDAY"), timezone} {type: "Monthly", every, startsOn, resetDay (1-28), timezone} assignment-cap reset — only for capping.resetPeriodicity: {type: "HourlyRolling"}|{type: "Daily", timeZone}|{type: "Weekly", timeZone, dayOfWeek (1-7)}|{type: "Monthly", timeZone} Note: every = reset every N days/weeks/months, counted from startsOn. Casing differs: timezone (meeting limit) vs timeZone (assignment cap). → {} ⚠ replace semantics (omitted assignees → weight 0); live immediately see: distribution-list (get distributionId and verify after), team-list (find teamId)

distribution-workspace-settings-get

ChatGPT
Returns the workspace-level round-robin settings that shape the fairness/leveling equation applied on top of each distribution's per-user weights and calibration. These knobs are shared by every distribution in the workspace, so an analysis skill should read them before reasoning about how assignments are balanced (otherwise it may misassume the leveling rules). Reflects the published (live) settings only. Pass the target workspaceId (from workspace-list). → {calibrateVacation, creditBackCancelled, creditBackNoShow, orderIfEqualState ("Random"|"AsConfigured"), resetPeriodicity, vacationBuffer: {daysBeforeStart, daysBeforeEnd, recognitionEnabled, freshness ("RealTime"|"Cached")}} - resetPeriodicity: discriminated by type: {type: "Monthly", timeZone} {type: "Quarterly", firstMonth (1-3), timeZone} {type: "Yearly", monthOfYear (1-12), timeZone} {type: "Never"} Note: creditBackCancelled / creditBackNoShow move a user back up the queue when a meeting is cancelled / no-showed; calibrateVacation excuses vacationing users from the fairness penalty. see: distribution-workspace-settings-update (edit these settings), workspace-list (resolve workspaceId), distribution-list-put (per-distribution weights/calibration)

distribution-workspace-settings-update

ChatGPT
Updates the workspace-level round-robin settings read by distribution-workspace-settings-get, publishing immediately (they apply to every distribution in the workspace). MERGE semantics, not replace: every field is optional — a field you omit keeps its current value, a field you send is overwritten. orderIfEqualState is derived upstream and cannot be set. Returns the full updated settings. - resetPeriodicity (opt): replaces the whole value; discriminated by type: {type: "Monthly", timeZone} {type: "Quarterly", firstMonth (1-3), timeZone} {type: "Yearly", monthOfYear (1-12), timeZone} {type: "Never"} - vacationBuffer (opt): replaces the whole value: {daysBeforeStart (>=0), daysBeforeEnd (>=0), recognitionEnabled, freshness ("RealTime"|"Cached")} → {calibrateVacation, creditBackCancelled, creditBackNoShow, orderIfEqualState, resetPeriodicity, vacationBuffer} ⚠ applies immediately to all distributions in the workspace see: distribution-workspace-settings-get (read current), workspace-list (resolve workspaceId)

distro-list-routers

ChatGPT
Browses every Distro router in the org to discover routerIds, activation status, and triggers. A Distro router routes CRM records (leads) to users via distributions, driven by a trigger. → {routers: [{id, name, status, trigger: {objectType, eventTypes: [{type, ...}], evaluation, delay}}]} Note: status is the router's activation state — one of {type:"Active"} | {type:"Inactive"} | {type:"Activating"} | {type:"Deactivating"} | {type:"Error", message}. A router created via the API is Inactive until distro-router-activate is called. Note: when an entry of trigger.eventTypes has type=Scheduled it carries schedulerId — that is the resourceSchedulerId expected by resource-scheduler-run. see: resource-scheduler-run (run a scheduled router on demand using schedulerId from a Scheduled trigger), distro-router-activate / distro-router-deactivate (toggle a router's status)

distro-log-get

ChatGPT
Drills into a single Distro log to explain a routing decision — the per-record evaluation trace that answers "why did this record route here / why didn't it route". Use after distro-logs to debug a specific record; take logId and routerId from the log entry. → {log: {id, workspaceId, routerId, status, assignments, trigger?, createdAt, updatedAt}, events: [{stage, status, trigger?, triggerValidation?, changeEvaluation?, entryRule?, route?, slaRules, enrichments, assignments, error?, createdAt, updatedAt}]} Note: stage is the internal processing-stage name of the event (e.g. "ActionsSuccessful", "RuleEvalFailedV2"); entryRule, route.rules and slaRules entries are rule evaluations: {ruleId?, ruleName?, matched, logic?, conditions: [{index, conditionId?, kind, field?, recordId?, resolvedValue?, operator?, expectedValue?, expectedField?, matched, owners, error?}]}. Note: each condition shows the data-source field as source.object.field with the rule-builder source code (e.g. "SF.Lead.Email" for Salesforce), the resolved record id and value, the expected value(s) and whether it matched; logic combines condition indexes, e.g. "(1 and 2) or 3". Enrichment steps list per-field existing vs enriched values; route.matchers and route.duplicateMatches cover path matching and duplicate-match ownership. see: distro-logs (list logs to obtain logId and routerId)

distro-logs

ChatGPT
Audits a workspace's Distro router runs — a paginated, record-level trail of which records were routed, to whom, and how. Use distro-log-get afterwards to drill into why a single record routed the way it did. Paging defaults to page 0 / pageSize 10. - body (req; send {} for no filtering): filter with optional fields: - userIds (opt): filter by assignee user IDs - status (opt): flow lifecycle outcome; one of: "NotTriggered" | "NotMatchedEntryRule" | "DelayInProgress" | "WorkingHours" | "SlaInProgress" | "NotRouted" | "Finished" | "SlaFinished" | "Error" - distributionMethod (opt): how the user was picked (a distribution-level decision, not a distro-level state); one of: "FromOwnershipArs" | "RoundRobinEvaluationSuccess" | "EvaluatedFromRoundRobinArs" | "FallbackTeam" | "FallbackUser" | "DuplicateMatchOwner" | "AssignmentTable" | "NoDistribution" | "NoUserAvailable" | "ClientError" - search (opt): text search - from (opt): start time (ISO8601 with zone, e.g. "2024-01-01T00:00:00Z") - to (opt): end time (ISO8601 with zone) → {results: [{log: {id, workspaceId, routerId, status, assignments: [{userId, distributionId?, method}], trigger?: {entityId, entityType, eventType}, createdAt, updatedAt}, router: {id, name, workspaceId}}], total, page, pageSize} see: distro-list-routers (find routers), distro-router-get (find a router's workspaceId)

distro-router-activate

ChatGPT
Turns a Distro router on (inactive → active) so it starts routing records. This is the step that makes a distro-router-create'd router (published but INACTIVE) live. Idempotent — activating an already-active router is a no-op. → {id, workspaceId, name?, description?, status, trigger: {objectType, eventTypes: [{type, ...}], evaluation?, delay?}, routing: {known, representable, rows, catchAll?, routingSteps}} Note: status is one of {type:"Active"} | {type:"Inactive"} | {type:"Activating"} | {type:"Deactivating"} | {type:"Error", message}; activation completes asynchronously, so status may read Activating until the backend settles it to Active ⚠ activation is REJECTED (422) if the backend validation fails (e.g. the trigger object type is not enabled for the workspace) — the router stays inactive; fix the cause and retry see: distro-router-create (creates a router inactive), distro-router-deactivate (turn it off), distro-router-get (check status)

distro-router-create

ChatGPT
Creates a Distro router and publishes it, but leaves it INACTIVE — it routes nothing until you call distro-router-activate. Publish is implicit; activation is a deliberate separate step. Distro routes CRM records (leads), so a router needs a trigger and its routes carry NO meeting type. workspaceId must be a team workspace of this tenant (400 otherwise). - routing (req): the lead-routing matrix — a trigger, ordered rules evaluated top-down, a mandatory catch-all, plus optional pre-distribution routing steps. Each route/catch-all carries an assignee (who) AND at least one of: CRM actions (what), convertLead, or addToCampaign (that minimum is required to publish): {trigger: {objectType, eventTypes: [{type, ...}], evaluation?, delay?}, routes: [{ruleId, assignee: assignee, actions: [action], convertLead?, addToCampaign?}], catchAll: {distributionId, actions: [action], convertLead?, addToCampaign?}, routingSteps?: [routingStep]} assignee (one of): {type: "Distribution", distributionId} | {type: "UseAssignmentTable"} | {type: "Owner"} action (one of): {type: "ReassignRecord"} | {type: "UpdateOwnership", respectWorkingHours?, sendSlackNotification?, sendEmailNotification?, matchBy?} | {type: "UpdateField", field, value?, objectType?} | {type: "UpdateFieldDynamic", field, value: dynamicValue, objectType?} | {type: "SendSlackToAssignee"} | {type: "SendEmailReminderToAssignee"} dynamicValue (one of): {type: "RelativeDate", unit: "Day"|"Week"|"Month"|"Quarter"|"Year", offset?, timeZone?} | {type: "TemplateText", text} | {type: "VariableField", variableId, field, relation?} convertLead: {convertedStatus, accountSettings: {type: "CreateNewAccount"} | {type: "MatchAccount"}, opportunitySettings: {type: "CreateNewOpportunity"} | {type: "DoNotCreateOpportunity"}} addToCampaign: {campaignId, status} routingStep (one of): {type: "Enrichment", id?, fieldMappings: [{variableId, field, waterfallId, overwriteExisting?, relation?}]} | {type: "SpamCheck", id?, salesforceWrite: {type: "Off"} | {type: "Enabled", scoreField?, otherFields: [{objectType, field, value?}]}} trigger.objectType is the CRM object (Lead, Contact, Account, Opportunity, …); trigger.eventTypes is a non-empty list of trigger events (e.g. {type:"NewRecord"}, {type:"UpdateField", expression}, {type:"Signal"}, {type:"Scheduled", schedulerId}). Each route row: if its rule matches, route the record to its assignee and run its actions. A row's assignee is Distribution (round-robin over distributionId), UseAssignmentTable (assignee looked up in the assignment table of the row's own ruleId, which must be an assignment-table rule — see rule-create) or Owner (keep the record's current owner); the legacy distributionId field on a row is still accepted as shorthand for a Distribution assignee (if both are sent they must name the same distribution). The catchAll takes only distributionId (Distro allows no other catch-all assignee). ruleId is required on every row; an always-match route is the catchAll (used when no row matches). routes may be empty (then only the catchAll applies). No meeting type anywhere — Distro routes leads, it does not book meetings. trigger.evaluation (opt) is a ruleId used as the router's entry rule (records that fail it are not routed); trigger.delay (opt) delays evaluation — {type:"AfterRecordEval"|"BeforeRecordEval", duration}. The assignee picks WHO gets the record; actions decide WHAT happens to the chosen assignee. A route publishes with at least one of {an action, convertLead, addToCampaign} — a route with none of these fails to publish (so a convert-lead-only route with no actions is valid). ReassignRecord (and UpdateOwnership, with optional working-hours / notification / Id-vs-Email match controls) assign the record's owner to the distributed user; UpdateField sets a CRM field to a static value (objectType defaults to the trigger object, omit value to clear it); UpdateFieldDynamic sets a field to a value resolved at routing time — a relative date ({type:"RelativeDate", unit, offset}), {!variable.Field} template text, or a variable-field reference; SendS…

distro-router-deactivate

ChatGPT
Turns a Distro router off (active → inactive) so it stops routing records. Also the prerequisite for deletion: an active router cannot be deleted, so deactivate and wait for Inactive before distro-router-delete. Idempotent — deactivating an already-inactive router is a no-op. Set force=true to deactivate even when the router's revision is stale. → {id, workspaceId, name?, description?, status, trigger: {objectType, eventTypes: [{type, ...}], evaluation?, delay?}, routing: {known, representable, rows, catchAll?, routingSteps}} Note: deactivation completes asynchronously, so status typically reads {type:"Deactivating"} on return and settles to {type:"Inactive"} shortly after — poll distro-router-get until Inactive before calling distro-router-delete see: distro-router-activate (turn it back on), distro-router-delete (delete once inactive), distro-router-get (check status)

distro-router-delete

ChatGPT
Permanently deletes a Distro router. The router must be INACTIVE first — deactivate it via distro-router-deactivate and wait until distro-router-get shows Inactive before deleting. ⚠ REJECTED (409) when the router is still active (or mid-transition) — deactivate it first via distro-router-deactivate, then delete once distro-router-get shows status inactive ⚠ irreversible via API; any triggers or links pointing at this router stop working see: distro-router-deactivate (deactivate before deleting), distro-list-routers or distro-router-get (confirm the id and status before deleting)

distro-router-get

ChatGPT
Fetches one Distro router: its identity, activation status, trigger, and a lossy per-row summary of its lead-routing. Call this before distro-router-update to read back the current routing (update overlays your changes onto it — matching rows by ruleId — and preserves advanced config it can't show you) and to check status. (Implemented by filtering the router list — there is no internal get-by-id endpoint.) → {id, workspaceId, name?, description?, status, trigger: {objectType, eventTypes: [{type, ...}], evaluation?, delay?}, routing: {known, representable, rows: [{ruleId?, outcome}], catchAll?: outcome, routingSteps: [routingStep]}} status (one of): {type: "Active"} | {type: "Inactive"} | {type: "Activating"} | {type: "Deactivating"} | {type: "Error", message} outcome (one of): {type: "Route", distributionId?, assignee?: assignee, actions: [action], convertLead?, addToCampaign?} | {type: "Unrepresentable", kind} assignee (one of): {type: "Distribution", distributionId} | {type: "UseAssignmentTable"} | {type: "Owner"} action (one of): {type: "ReassignRecord"} | {type: "UpdateOwnership", respectWorkingHours?, sendSlackNotification?, sendEmailNotification?, matchBy?} | {type: "UpdateField", field, value?, objectType?} | {type: "UpdateFieldDynamic", field, value: dynamicValue, objectType?} | {type: "SendSlackToAssignee"} | {type: "SendEmailReminderToAssignee"} dynamicValue (one of): {type: "RelativeDate", unit: "Day"|"Week"|"Month"|"Quarter"|"Year", offset?, timeZone?} | {type: "TemplateText", text} | {type: "VariableField", variableId, field, relation?} convertLead: {convertedStatus, accountSettings: {type: "CreateNewAccount"} | {type: "MatchAccount"}, opportunitySettings: {type: "CreateNewOpportunity"} | {type: "DoNotCreateOpportunity"}} addToCampaign: {campaignId, status} routingStep (one of): {type: "Enrichment", id?, fieldMappings: [{variableId, field, waterfallId, overwriteExisting?, relation?}]} | {type: "SpamCheck", id?, salesforceWrite: {type: "Off"} | {type: "Enabled", scoreField?, otherFields: [{objectType, field, value?}]}} | {type: "Unrepresentable", kind} Note: a Route outcome carries the assignee (who: a round-robin distribution, the row rule's assignment table, or the record owner; distributionId is also set for a Distribution assignee), the CRM actions performed on the chosen assignee (what), and the two round-trippable route-level side effects — convertLead (convert a matched Lead to a Contact; only when the trigger object is Lead) and addToCampaign (add the record to a Salesforce campaign); it round-trips into distro-router-update. Unrepresentable rows use app-only features and carry a short kind, not the detail — e.g. "side-effect" (SLAs, send-to-routers), "matchers", "app-only-action", "no-assignee" (no distribution), "assignee-fallback" (unavailable / capping fallbacks), "explicit-assignment" — but an update still edits them safely: the overlay changes only the assignee + actions + convertLead + addToCampaign you send for a matched ruleId and leaves that advanced config untouched Note: trigger is the same shape distro-list-routers returns and distro-router-update accepts as routing.trigger, so it round-trips as-is (a null evaluation or delay means none and is accepted on write; drop null fields if your client validates arguments against the input schema, where both are optional but not nullable); when an entry of trigger.eventTypes has type=Scheduled it carries schedulerId (the resourceSchedulerId for resource-scheduler-run) Note: routingSteps are the pre-distribution steps (enrichment, spam-check) the router runs before the rows; they round-trip into distro-router-update. A routing step Edge cannot model reads back as {type:"Unrepresentable", kind} Note: routing.representable indicates only whether this lossy summary round-trips EXACTLY (every row, the catch-all AND every routing step representable); it is advisory — an update is accepted either way, because the overlay preserves advanced config rather than replacing it Note: status is the router's activation state; a router created via the API is Inac…

distro-router-update

ChatGPT
Edits a Distro router and republishes it, preserving its activation — an active router stays active with the new config live immediately, an inactive one stays inactive (use distro-router-activate / distro-router-deactivate to change activation deliberately). routing is REQUIRED; it is OVERLAID onto the router's current routing (routes matched by ruleId), NOT a blind full-replace — advanced config Edge can't model (SLAs, matchers, duplicate-matching, assignee fallbacks, app-only actions) is preserved, so ANY router can be edited (there is no longer a not-representable rejection). To change only name/description, resend the current routing (from distro-router-get) alongside them. - name (opt): new display name - description (opt): new description - routing (req): the lead-routing matrix, applied as an OVERLAY. The trigger and routing steps are replaced from what you send; each route/catch-all is matched to the router's existing routing by ruleId (catch-all to catch-all) and its assignee (who), CRM actions (what), convertLead and addToCampaign are swapped in from the patch (a row sent without an assignee keeps its current one; sending convertLead / addToCampaign absent clears them on that row) — matchers, SLAs, send-to-routers, duplicate-matching and any app-only actions on that route are PRESERVED. A row whose ruleId matches nothing is appended as a new route. Distro still requires at least one of {an action, convertLead, addToCampaign} per route AND on the catch-all to publish (a matched route keeps its existing actions, so supply actions only where you change them or on new rows): {trigger: {objectType, eventTypes: [{type, ...}], evaluation?, delay?}, routes: [{ruleId, assignee: assignee, actions: [action], convertLead?, addToCampaign?}], catchAll: {distributionId, actions: [action], convertLead?, addToCampaign?}, routingSteps?: [routingStep]} assignee (one of): {type: "Distribution", distributionId} | {type: "UseAssignmentTable"} | {type: "Owner"} action (one of): {type: "ReassignRecord"} | {type: "UpdateOwnership", respectWorkingHours?, sendSlackNotification?, sendEmailNotification?, matchBy?} | {type: "UpdateField", field, value?, objectType?} | {type: "UpdateFieldDynamic", field, value: dynamicValue, objectType?} | {type: "SendSlackToAssignee"} | {type: "SendEmailReminderToAssignee"} dynamicValue (one of): {type: "RelativeDate", unit: "Day"|"Week"|"Month"|"Quarter"|"Year", offset?, timeZone?} | {type: "TemplateText", text} | {type: "VariableField", variableId, field, relation?} convertLead: {convertedStatus, accountSettings: {type: "CreateNewAccount"} | {type: "MatchAccount"}, opportunitySettings: {type: "CreateNewOpportunity"} | {type: "DoNotCreateOpportunity"}} addToCampaign: {campaignId, status} routingStep (one of): {type: "Enrichment", id?, fieldMappings: [{variableId, field, waterfallId, overwriteExisting?, relation?}]} | {type: "SpamCheck", id?, salesforceWrite: {type: "Off"} | {type: "Enabled", scoreField?, otherFields: [{objectType, field, value?}]}} trigger.objectType is the CRM object (Lead, Contact, Account, Opportunity, …); trigger.eventTypes is a non-empty list of trigger events ({type:"NewRecord"}, {type:"UpdateField", expression}, {type:"Signal"}, {type:"Scheduled", schedulerId}). Each route row: if its rule matches, route to its assignee (Distribution | UseAssignmentTable | Owner, or the legacy distributionId shorthand — see distro-router-create) and run its actions; the catchAll takes only distributionId. ruleId is required on every row; an always-match route is the catchAll. routes may be empty (then only the catchAll applies). No meeting type — Distro routes leads. trigger.evaluation (opt) is a ruleId used as the router's entry rule (records that fail it are not routed); trigger.delay (opt) delays evaluation — {type:"AfterRecordEval"|"BeforeRecordEval", duration}. The assignee picks WHO; actions decide WHAT (assign owner, set a field, notify the assignee). A route publishes with at least one of {an action, convertLead, addToCampaign}. convertLead (convert a matched Lead to a Contact) REQUIRES tri…

enrichment-waterfall-list

ChatGPT
Lists a workspace's enrichment waterfalls (read-only). A waterfall is an ordered set of provider-qualified enrichment fields (e.g. LeadIq.Person.FullName, Apollo.Company.Industry) that the enrichment engine tries in order until one returns a value; a waterfall may be tied to the data field it populates. Custom waterfalls are tenant-authored; templates are built-in. Paging defaults to page 0 / pageSize 200. → {results: [{id, name, isCustom, isTemplate, fields: ["LeadIq.Person.FullName", ...], tiedToDataField?, exclusivelyFor?, metadata}], total, page, pageSize} see: data-field-list (the data fields these waterfalls can populate)

handoff-init

ChatGPT
Phase 1 of 2 of a handoff: launches the workspace's routers, evaluates assignee availability, stores per-path scheduling sessions, and returns routing paths with available slots. Pick one (routerId, pathId, startTime) from the response and pass it to handoff-schedule to actually book — this call books nothing on its own. Identify the initiating rep by their CP userId. - body (req): InitRequest — either {type:"GuestEmailRequest", guestEmail, crmExplicits, interval} or {type:"CrmRequest", id, crmExplicits, interval} - meetingTypeOverride (opt): per-booking length override: {meetingDurationOverride, beforeBufferDurationOverride, afterBufferDurationOverride, availabilityIncrementOverride} Note: each field is an optional duration string like "15 minutes". When set, the returned slots reflect the overridden length and the override is persisted on the session so handoff-schedule books at the same length. Bounds are validated by the availability service. → {routingId, guestMatch, guestEmail, routers: [{routerId, pathResults: [ {type:"Later", pathId, assignees, meetingHost, additionalAttendees, duration, locations, startTimes} | {type:"Fork", live:{pathId, assignees, duration, locations}, later:{pathId, ..., duration, locations, startTimes}} ]}]} each startTimes entry exposes startTime + attendees (unified RequestedAttendee model — 5 variants discriminated by type) pick one (routerId, pathId, startTime) and call handoff-schedule with just startTime Note: a Fork's live path has no startTimes — it books "now"; each live assignee carries availability (Available = free right now). To book it call handoff-schedule with live.pathId and {live: true} Note: when a rule's Live half has no match (e.g. nobody in the Live team) the rule comes back as a plain Later path ⚠ session has TTL — if handoff-schedule is not called before expiry, call handoff-init again see: workspace-list (workspaceId), user-find (userId), handoff-schedule (required next step)

handoff-log-get

ChatGPT
Drills into one Handoff routing to explain the decision — for every evaluated router, which paths matched, which did not and why, plus the selection and the meeting outcome. Take routingId from handoff-logs (or from handoff-init). → {log: {routingId, workspaceId, routerIds, status, createdAt, updatedAt, guestEmail?, crmRecordId?, guestMatch?, launchRejection?, selection?, meeting?}, routers: [{routerId, paths: [{pathId, catchAll, liveFork?, ruleIds, outcome, reason?, distributionId?, assignees, meetingTypeId?}]}]} Note: outcome is one of "Matched" | "NotMatched" | "Errored" | "Died" | "NotExisting"; reason explains NotMatched (RuleIsFalse), Errored (OwnerNotInTeam, NoRowMatched, NoUserInDistribution) and Died Note: liveFork is "Live" or "Later" when a path forks into a Handoff Live branch and a later-booking branch see: handoff-logs (list routings to obtain routingId)

handoff-logs

ChatGPT
Audits a workspace's Handoff routings — who launched a handoff for which guest, which router path was selected, who it was assigned to and whether the meeting was booked. Newest first. Use handoff-log-get afterwards to see why each router path matched or not. - workspaceId (req): workspace to audit - from, to (req): routing creation time range (ISO8601 with zone, e.g. "2026-10-01T00:00:00Z"); from inclusive, to exclusive, at most 31 days apart - routerId (opt): only routings that evaluated this router - cursor (opt): nextCursor from the previous page - limit (opt): page size, default 25, max 100 → {items: [{routingId, workspaceId, routerIds, status, createdAt, updatedAt, guestEmail?, crmRecordId?, guestMatch?, launchRejection?: {hint, errorCode}, selection?: {routerId, pathId, assignees, host, additionalAttendees, meetingTypeId, bookerId}, meeting?: {booked, meetingId?, assigneeIds, failureDetails?}}], nextCursor?} Note: status is one of "LaunchRejected" | "Launched" | "PathSelected" | "MeetingRequested" | "MeetingRejected" | "MeetingBooked" | "Unknown" (stored state could not be read) Note: guest data that was anonymized stays anonymized see: handoff-log-get (path evaluation of one routing), handoff-router-list (find a routerId), workspace-list (find a workspaceId)

handoff-router-create

ChatGPT
Creates a Handoff router and publishes it live in one step — there is no unpublished-draft state via the API. workspaceId must be a team workspace of this tenant (400 otherwise). The routing matrix is a list of ordered rules evaluated top-down plus an optional catch-all fallback: - routing (req): ordered rules evaluated top-down, plus an optional catch-all fallback. Each row's rule, when it matches, runs that row's outcome; the catch-all outcome runs when no row matches: {routes: [{ruleId, outcome}], catchAll?: outcome} outcome: {type: "Schedule", assignment, meetingTypeId, crmActions?} Schedule assigns the lead and books a meeting type. assignment is {type: "Distribution", distributionId} (round-robin / ownership / whatever the distribution does), {type: "User", userId} (a specific host), or {type: "UseAssignmentTable", ruleId, columnKey} (the host comes from column columnKey of the assignment-table row that rule ruleId matched; only on a row whose ruleId is that same assignment-table rule, never on the catchAll — find columnKey with assignment-table-definition-get). crmActions (opt) is an ordered post-booking chain (ConvertLead is rejected with 400 — Handoff does not execute it; use a Distro router's convertLead), each {type: "AddToCampaign", campaignId, memberStatus} (add the matched Salesforce lead/contact to a campaign with that member status; find campaignId with campaign-list / campaign-search), {type: "SalesforceUpdateFields", contact:[{object,field,value}], lead:[{field,value}]} (write field values onto the matched Salesforce contact/lead), {type: "HubspotUpdateFields", contact:[{object,field,value}]} (write property values onto the matched HubSpot record), {type: "SalesforceUpdateOwnership", contact:[{object,field}], lead:[{field}]} (set the record owner to the booked host), {type: "HubspotUpdateOwnership", contact:[{object,field}]} (set the record owner to the booked host), {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?} (create a Salesforce Event for the booked meeting; relatedTo links it to a CRM object — {type: "Account"|"Opportunity"|"Case"} or {type: "Campaign", campaignId} — omit for no relation; meetingCancellationBehavior DeleteEvent|DoNothing; guestsBehavior CreateEvents|DoNothing), or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?} (create a HubSpot meeting engagement; relatedTo {type: "Company"|"Deal"|"Ticket"}; owner Assignee|Booker). At most one action of each type per chain — a duplicated type is rejected (400); put all rows of one kind in a single action. ruleId is required on every row; an always-match route is the catchAll. catchAll is optional — a router with no catch-all simply has no fallback when no row matches. Provide at least one row or a catchAll (an empty matrix with neither is rejected). Note: handoff routers support only the Schedule outcome — Redirect outcomes, no-show timeouts, Notify (Slack), and the SalesforceUpsertRecord / HubspotUpsertRecord create-record CRM actions are concierge-only and are not part of the handoff schema (AddToCampaign / SalesforceUpdateFields / HubspotUpdateFields / SalesforceUpdateOwnership / HubspotUpdateOwnership / SalesforceCreateEvent / HubspotCreateEngagement are the only CRM actions here). → {id, workspaceId, name?, routing: {known, representable, rows: [{ruleId?, ruleType?, outcome}], catchAll?: {outcome}}} Note: the returned routing is the lossy per-row summary (same shape as handoff-router-get), not the matrix you sent ⚠ create is all-or-nothing: if the final publish step rejects the router, the router is deleted again and a typed 422 error with the backend reason is returned — nothing is left behind, so fix the request and retry ⚠ every distributionId the request names must exist in this tenant — an unknown one is rejected (404) before anything is written see: handoff-router-list (find a workspaceId), rule-list (find ruleIds), distribution-list (find distributionIds), user-find (find userIds), meeting-type-list (find meetingTypeIds), assignment-table-definition-ge…

handoff-router-delete

ChatGPT
Permanently deletes a Handoff router. ⚠ irreversible via API; any links or integrations pointing at this router stop working see: handoff-router-list or handoff-router-get (confirm the id before deleting)

handoff-router-get

ChatGPT
Fetches one Handoff router: its identity plus a lossy per-row summary of what its routing does. Call this before handoff-router-update to check whether the router's routing is representable (safe to replace via the API). → {id, workspaceId, name?, routing: {known, representable, rows: [{ruleId?, ruleType?, outcome}], catchAll?: {outcome}}} outcome (one of): {type: "Schedule", distributionId?, userId?, meetingTypeId?, crmActions?} | {type: "Redirect", url?} | {type: "OwnerAssign"} | {type: "ContactOptions", meeting?, callSuccess?, callMissed?} | {type: "CrmAction"} | {type: "Other", kind} Note: on a Schedule, crmActions is the readable post-booking chain — each {type: "ConvertLead"} (stored on older routers but NOT executed by Handoff), {type: "AddToCampaign", campaignId, memberStatus}, {type: "SalesforceUpdateFields", contact, lead}, {type: "HubspotUpdateFields", contact}, {type: "SalesforceUpdateOwnership", contact, lead}, {type: "HubspotUpdateOwnership", contact}, {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?}, or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?}; a node Edge can't model appears as {type: "Other", kind} (and representable is false) instead of nulling the whole chain Note: the summary is lossy by design — it NAMES each row's outcome (including ones Edge can't itself produce) so you can see what the router does even when it was built in the app Note: routing.representable is true only when the current routing — both the draft tree and the published tree — is exactly what Edge's create/update would have produced; when false, an update with a new routing may be rejected (edit it in the Handoff app instead). A router that has no routing tree yet also reports representable=false, but an update setting routing on it is still accepted (nothing is destroyed). see: handoff-router-list (browse to find a routerId), handoff-router-update (replace routing if representable)

handoff-router-list

ChatGPT
Browses Handoff routers to discover routerIds and see what each one routes. A Handoff router routes SDR-to-AE handoffs to teams/users via rules. Each entry carries the router's identity plus a lossy per-row summary of its routing. Pass workspaceId to restrict to one workspace (must belong to this tenant); omit it to fan out across all workspaces. → [{id, workspaceId, name?, routing: {known, representable, rows: [{ruleId?, ruleType?, outcome}], catchAll?: {outcome}}}] Note: the routing summary is fully populated and has the same lossy-by-design shape as handoff-router-get see: workspace-list (find a workspaceId), handoff-router-get (fetch one), handoff-router-update (replace config)

handoff-router-update

ChatGPT
Edits a Handoff router and republishes it live. Only the fields you supply change; omitted fields are preserved. Use handoff-router-get first to confirm the routing is representable before replacing it. - routing (opt): when present, sets the routing matrix; when omitted, the current routing is kept. Each row's rule, when it matches, runs that row's outcome; the catch-all outcome runs when no row matches: {routes: [{ruleId, outcome}], catchAll?: outcome} outcome: {type: "Schedule", assignment, meetingTypeId, crmActions?} Schedule assigns the lead and books a meeting type. assignment is {type: "Distribution", distributionId}, {type: "User", userId}, or {type: "UseAssignmentTable", ruleId, columnKey} (the host comes from column columnKey of the assignment-table row that rule ruleId matched; only on a row whose ruleId is that same assignment-table rule, never on the catchAll — find columnKey with assignment-table-definition-get). crmActions (opt) is an ordered post-booking chain, each {type: "ConvertLead"} (kept only on a route that already has it — Handoff does not execute it, so adding it is rejected with 400; use a Distro router's convertLead), {type: "AddToCampaign", campaignId, memberStatus} (add the matched Salesforce lead/contact to a campaign with that member status; find campaignId with campaign-list / campaign-search), {type: "SalesforceUpdateFields", contact:[{object,field,value}], lead:[{field,value}]} (write field values onto the matched Salesforce contact/lead), {type: "HubspotUpdateFields", contact:[{object,field,value}]} (write property values onto the matched HubSpot record), {type: "SalesforceUpdateOwnership", contact:[{object,field}], lead:[{field}]} (set the record owner to the booked host), {type: "HubspotUpdateOwnership", contact:[{object,field}]} (set the record owner to the booked host), {type: "SalesforceCreateEvent", relatedTo?, meetingCancellationBehavior?, guestsBehavior?} (create a Salesforce Event for the booked meeting; relatedTo links it to a CRM object — {type: "Account"|"Opportunity"|"Case"} or {type: "Campaign", campaignId} — omit for no relation; meetingCancellationBehavior DeleteEvent|DoNothing; guestsBehavior CreateEvents|DoNothing), or {type: "HubspotCreateEngagement", relatedTo?, owner?, meetingCancellationBehavior?} (create a HubSpot meeting engagement; relatedTo {type: "Company"|"Deal"|"Ticket"}; owner Assignee|Booker). At most one action of each type per chain — a duplicated type is rejected (400); put all rows of one kind in a single action. ruleId is required on every row; an always-match route is the catchAll. routes may be empty (then only the catchAll applies). Note: catchAll is OPTIONAL on update — omit it to keep the router's current catch-all unchanged (a targeted edit of the rows only), including keeping a router that has no catch-all catch-all-less. Supply it to replace (or add) the catch-all outcome. Note: handoff routers support only the Schedule outcome — Redirect outcomes, no-show timeouts, Notify (Slack), and the SalesforceUpsertRecord / HubspotUpsertRecord create-record CRM actions are concierge-only and are not part of the handoff schema (AddToCampaign / SalesforceUpdateFields / HubspotUpdateFields / SalesforceUpdateOwnership / HubspotUpdateOwnership / SalesforceCreateEvent / HubspotCreateEngagement are the only CRM actions here). → {id, workspaceId, name?, routing: {known, representable, rows: [{ruleId?, ruleType?, outcome}], catchAll?: {outcome}}} Note: the returned routing is the lossy per-row summary (same shape as handoff-router-get), not the matrix you sent ⚠ publishes live immediately. For a representable router, routing replaces the whole matrix; for an app-built (non-representable) router, your rows are OVERLAID onto the existing tree by ruleId — the modeled dimensions (assignment, meeting type, CRM chain, and catch-all when supplied) are replaced in place and every app-only feature is preserved. Check routing.representable via handoff-router-get first. ⚠ every update publishes the router's current DRAFT — if the draft carries unpublished edits made in…

handoff-schedule

ChatGPT
Phase 2 of 2 of a handoff: books a meeting on the (routerId, pathId, startTime) chosen from the handoff-init response, creating calendar events and sending confirmations. Pass the same CP userId used in handoff-init; startTime must be one of the startTimes handoff-init returned for that (routerId, pathId). - body (req): ScheduleSimpleRequest — preferred shape is just {startTime} (+ optional location, guestTimezone, additionalGuests — array of {email, required?} (required defaults to true; set false to invite as an optional attendee), each added to the calendar invite as an external attendee). All other fields (meetingTypeId, meetingInvite, crmExplicits, additionalAttendees, assignees, host, timeInterval, meetingTypeRelevantAt) are deprecated legacy inputs, ignored when startTime is provided — except a non-empty additionalAttendees, which is rejected with 400 (use additionalGuests, or configure additional attendees on the handoff router). Note: additionalGuests are invited by email only — their calendar availability is NOT checked (only the assignees' is). - live (opt): true books Handoff Live on a Fork's live.pathId — omit startTime; the meeting starts in 1 minute with a live assignee who is free right now (first in routing order, or assigneeId if given) Note: 409 when no live assignee is free (skipped when the workspace's handoff settings let bookers pick unavailable reps). On 409, book one of the Fork's later.startTimes instead → {meetingTypeId, host, selectedAssignees, additionalAttendees, scheduleMeetingResult: {type:"Success", meetingId} | {type:"Failure", details}} ⚠ calendar invites sent immediately — to undo call meeting-cancel; session expiry causes failure — call handoff-init again; double-booking possible if same slot used in concurrent sessions see: handoff-init (mandatory prerequisite), meeting-get/activity (view booked meeting)

handoff-select-simple

ChatGPT
Downstream of handoff-init: for a chosen (routerId, pathId), generates booking artifacts (a suggested-times widget and/or a single-use scheduling link) to hand to the guest, without booking anything. Reuses the path's start times persisted by handoff-init, so it makes no extra availability call. Use this instead of handoff-schedule when you want the guest to pick the slot rather than booking one yourself. - body (req): SelectSimpleRequest — {outputs: [{type:"SuggestedTimes", slotCount?, slots?} | {type:"SingleUseLink"}], guestTimezone?} → {suggestedTimesWidget?: {html, slotIds}, singleUseLink?: {url}} see: handoff-init (mandatory prerequisite), handoff-schedule (to book a specific slot)

health-ping

ChatGPT
Verifies API key is valid and service is reachable. Call first in a session — if this fails, all other calls will too. → "ok" ⚠ 401 if key is missing/revoked; 5xx if service unavailable

integration-connection

ChatGPT
Returns the org-wide (tenant-level) connection status for a single integration. Uniform and CRM-agnostic (status only, no CRM-specific metadata); the org-level connection is the right surface for "is this integration connected" — not the per-user find-users view. Consistent with the in-app integrations UI: org-credential integrations (Salesforce, Hubspot, Slack, Gong, Outreach, Salesloft) report the single org connection read from the backing service directly; every other (per-user) integration is the roll-up of its per-user statuses — Connected when every connected user is Connected, otherwise the most-severe status among them. - integration (required): integration service name (e.g. "Salesforce", "Hubspot", "Google") → {integration, status} Note: status is one of Connected, InTrouble, Disconnected, NeverConnected; for a per-user integration NeverConnected means no user has a connection, for an org-credential integration it means the org has never connected it see: integration-find-users (per-user connection status)

integration-find-users

ChatGPT
Returns a paginated list of users with, per integration service, their connection status and CRM user-mapping status. Use this to audit who is disconnected, in trouble, or unmapped for a given integration (Salesforce, HubSpot, Google, etc.). Both maps come from the same aggregated source the in-app UI uses, for consistency. - integrations (opt): list of integration service names to include in the response (e.g. ["Salesforce","Google"]); omit to return all known connections/mappings for each user Note: when specified, users with no record for an integration are returned with connection status "NeverConnected" and mapping status "Unmapped" Note: "PhoneVerified" is a special dialer integration — its connection status reports whether the user has a verified outgoing caller id (Connected = can accept live calls, else NeverConnected). It is sourced from the dialer service, has no mapping status, and is only resolved when explicitly listed in integrations (never appears when integrations is omitted) - statuses (opt): filter to users who have at least one matching status across the requested integrations; supported values: Connected, InTrouble, Disconnected, NeverConnected (default: all) Note: NeverConnected requires integrations to be specified (returns 400 otherwise, since without a list of integrations there is no defined set to check against) - page (opt, default 0), pageSize (opt, default 10) → {results: [{userId, email, name, connections: {IntegrationService: status}, mappings: {IntegrationService: status}, notifiedAt}], total, page, pageSize} Note: connections maps integration service name to connection status (e.g. {"Salesforce":"Disconnected","Google":"Connected"}); mappings maps integration service name to CRM user-mapping status (Mapped or Unmapped); notifiedAt lists the last-notified time per integration if the user received a disconnection notification ⚠ in-memory filtering: the backend fetches up to 5000 users per call; tenants with more than 5000 users may receive incomplete results see: user-find-by-filter (general-purpose user search), user-read (full user profile by id)

integration-gong-set-mappings

ChatGPT
Replaces the ENTIRE set of Chili Piper ↔ Gong user mappings for the tenant with the submitted map. This is a strict full-replace, not a merge: any CP user NOT present in the map is left unmapped, and an empty map clears ALL Gong mappings for the tenant. Gong-only; use integration-salesforce-set-mappings / integration-hubspot-set-mappings for the CRMs. - body: a JSON object keyed by Chili Piper user id, whose value is the Gong user's email to map that CP user to (e.g. {"<cpUserId>": "rep@example.com"}); send {} to clear all mappings Note: Gong identifies users by email — the value is the Gong user's email, not a numeric id ⚠ atomic: if ANY submitted email does not resolve to a real Gong user, the WHOLE request is rejected (422) and nothing is changed ⚠ every key must be a Chili Piper user id of this tenant (look up ids with user-find); any unknown key → 422 edge.integration-mapping-unknown-users listing them, and nothing is changed → {UserId: {email}} Note: response echoes the resulting full mapping set (the same shape as integration-gong-users), so the caller sees exactly what is now mapped see: integration-gong-users (read current mappings)

integration-gong-users

ChatGPT
Resolves the given Chili Piper user ids to their mapped Gong users. Gong identifies users by email, so each mapped user is just the Gong email. Gong-only; use integration-salesforce-users / integration-hubspot-users for the CRMs. - userIds: Chili Piper user ids to resolve → {UserId: {email}} Note: response is a JSON object keyed by Chili Piper user id; unmapped users are omitted see: integration-find-users (CRM-agnostic mapping status), integration-gong-set-mappings (replace mappings)

integration-hubspot-set-mappings

ChatGPT
Replaces the ENTIRE set of Chili Piper ↔ HubSpot user mappings for the tenant with the submitted map. This is a strict full-replace, not a merge: any CP user NOT present in the map is left unmapped, and an empty map clears ALL HubSpot mappings for the tenant. HubSpot-only; use integration-salesforce-set-mappings for Salesforce. - body: a JSON object keyed by Chili Piper user id, whose value is the bare HubSpot user id to map that CP user to (e.g. {"<cpUserId>": 12345678}); send {} to clear all mappings Note: only the bare HubSpot id is required — edge resolves each id against live HubSpot to fill in owner id, internal user id, and email ⚠ atomic: if ANY submitted HubSpot id does not resolve to a real HubSpot user (or the resolved user has no email), the WHOLE request is rejected (422) and nothing is changed ⚠ every key must be a Chili Piper user id of this tenant (look up ids with user-find); any unknown key → 422 edge.integration-mapping-unknown-users listing them, and nothing is changed → {UserId: {id, email}} Note: response echoes the resulting full mapping set (the same shape as integration-hubspot-users), so the caller sees exactly what is now mapped see: integration-hubspot-users (read current mappings), integration-salesforce-set-mappings (Salesforce equivalent)

integration-hubspot-tenant

ChatGPT
Fetches HubSpot-specific account configuration for the authenticated tenant — the connected account's portal id, UI domain, and account type. The tenant is inferred from the API key, so there are no inputs. HubSpot-only; use integration-salesforce-tenant for Salesforce. → {portalId, uiDomain, accountType} see: integration-find-users (CRM-agnostic connection status), integration-salesforce-tenant (Salesforce equivalent)

integration-hubspot-users

ChatGPT
Resolves the given Chili Piper user ids to their mapped HubSpot users (HubSpot id and email). HubSpot does not expose name or active status. HubSpot-only; use integration-salesforce-users for Salesforce. - userIds: Chili Piper user ids to resolve → {UserId: {id, email}} Note: response is a JSON object keyed by Chili Piper user id; unmapped users are omitted see: integration-find-users (CRM-agnostic mapping status), integration-salesforce-users (Salesforce equivalent)

integration-salesforce-set-mappings

ChatGPT
Replaces the ENTIRE set of Chili Piper ↔ Salesforce user mappings for the tenant with the submitted map. This is a strict full-replace, not a merge: any CP user NOT present in the map is left unmapped, and an empty map clears ALL Salesforce mappings for the tenant. Salesforce-only; use integration-hubspot-set-mappings for HubSpot. - body: a JSON object keyed by Chili Piper user id, whose value is the bare Salesforce user id to map that CP user to (e.g. {"<cpUserId>": "0055g00000AbCdEfGh"}); send {} to clear all mappings Note: only the bare Salesforce id is required — edge resolves each id against live Salesforce to fill in email, name, and active flag ⚠ each Salesforce id must be 15 or 18 alphanumeric characters, and the last 3 chars of an 18-char id must be a valid Salesforce case-checksum suffix (A-Z or 0-5, any case), else 400 edge.bad-request (nothing is changed) Note: ids are matched case-safely: a 15-char id and its 18-char form are the same user, and an 18-char id is read case-insensitively (its case comes from the suffix); ids are echoed and reported in the canonical 18-char form ⚠ atomic: if ANY submitted Salesforce id does not resolve to a real Salesforce user, the WHOLE request is rejected (422) and nothing is changed; if Salesforce itself rejects the lookup → 400 edge.salesforce-request-rejected with the Salesforce reason ⚠ every key must be a Chili Piper user id of this tenant (look up ids with user-find); any unknown key → 422 edge.integration-mapping-unknown-users listing them, and nothing is changed → {UserId: {id, email, name, active}} Note: response echoes the resulting full mapping set (the same shape as integration-salesforce-users), so the caller sees exactly what is now mapped see: integration-salesforce-users (read current mappings), integration-hubspot-set-mappings (HubSpot equivalent)

integration-salesforce-tenant

ChatGPT
Fetches Salesforce-specific org configuration for the authenticated tenant — the connected org's instance URL, organization id, and whether it is a sandbox. The tenant is inferred from the API key, so there are no inputs. Salesforce-only; use integration-hubspot-tenant for HubSpot. → {instanceUrl, organizationId, isSandbox} see: integration-find-users (CRM-agnostic connection status), integration-hubspot-tenant (HubSpot equivalent)

integration-salesforce-users

ChatGPT
Resolves the given Chili Piper user ids to their mapped Salesforce users, including the Salesforce id, email, display name, and active flag. Use it to spot deactivated Salesforce users behind CP mappings. Salesforce-only; use integration-hubspot-users for HubSpot. - userIds: Chili Piper user ids to resolve → {UserId: {id, email, name, active}} Note: response is a JSON object keyed by Chili Piper user id; unmapped users are omitted see: integration-find-users (CRM-agnostic mapping status), integration-hubspot-users (HubSpot equivalent)

integration-salesloft-add-to-cadence

ChatGPT
Adds a prospect to a Salesloft cadence, resolving the prospect and assignee against Salesloft. The prospect is referenced by email and/or CRM record id (email is preferred when both are given); the assignee defaults to the prospect's owner and the cadence step defaults to the cadence's first step. Confirm-first by design — run it after a rep has confirmed the action, since it enrolls the prospect in a live cadence. - cadenceId (required): Salesloft cadence id to add the prospect to - prospectEmail / prospectCrmId (at least one required): who to add; email is preferred when both are supplied - assigneeEmail / assigneeCrmId (opt): the Salesloft user to own the membership; defaults to the prospect's owner - cadenceStepId (opt): step to start on; defaults to the cadence's first step → {prospectId, userId, cadenceId} Note: ids are the Salesloft-resolved prospect, assignee user, and cadence ⚠ writes to Salesloft: this enrolls the prospect in an active cadence and may trigger outreach — use confirm-first ⚠ 409 edge.salesloft-prospect-already-in-cadence if the prospect is already enrolled (nothing changes); 404 edge.salesloft-prospect-not-found if no prospect matches; 422 edge.salesloft-request-rejected if Salesloft rejects the request (e.g. invalid cadence or step, cadence has no steps, assignee does not own the cadence); 422 edge.salesloft-not-connected if Salesloft is not connected see: integration-find-users (per-user integration connection status)

integration-salesloft-cadence-steps

ChatGPT
Lists the steps of a Salesloft cadence so you can find the cadenceStepId to pass to integration-salesloft-add-to-cadence (which otherwise starts at the cadence's first step). Paginated. - cadenceId (required): the cadence whose steps to list - page (opt, default 0), pageSize (opt, default 10) → {results: [{id, name, displayName, stepType, disabled, day, stepNumber, multitouchEnabled, createdAt, updatedAt}], total, page, pageSize} Note: id is the cadenceStepId; salesloft only sorts steps by created/updated time, not stepNumber ⚠ 422 edge.salesloft-not-connected if Salesloft is not connected for the tenant (or its connection expired) see: integration-salesloft-cadences (list cadences), integration-salesloft-add-to-cadence

integration-salesloft-cadences

ChatGPT
Lists the tenant's Salesloft cadences so you can find the cadenceId to pass to integration-salesloft-add-to-cadence. Paginated. - page (opt, default 0), pageSize (opt, default 10) → {results: [{id, name, state, tags, teamCadence, shared, draft, archived, createdAt, updatedAt}], total, page, pageSize} Note: id is the cadence id to pass to integration-salesloft-add-to-cadence ⚠ 422 edge.salesloft-not-connected if Salesloft is not connected for the tenant (or its connection expired) see: integration-salesloft-add-to-cadence (add a prospect to a cadence)

integration-salesloft-prospect-find

ChatGPT
Looks up a single Salesloft prospect by email and/or CRM record id — use it to check whether a prospect already exists (and get their id) before enrolling them with integration-salesloft-add-to-cadence. At least one of email / crmId is required; email is preferred when both are given. - email (opt): prospect email to match - crmId (opt): prospect CRM record id to match → {prospect: {id, email, crmId, crmObjectType}} Note: prospect is null when no match is found ⚠ 400 if neither email nor crmId is provided; 422 edge.salesloft-not-connected if Salesloft is not connected for the tenant see: integration-salesloft-add-to-cadence

integration-salesloft-users

ChatGPT
Lists the tenant's Salesloft users — useful to resolve an assignee for integration-salesloft-add-to-cadence (whose assigneeEmail/assigneeCrmId default to the prospect's owner). Paginated. - page (opt, default 0), pageSize (opt, default 10) → {results: [{id, email, name, firstName, lastName, createdAt, updatedAt}], total, page, pageSize} ⚠ 422 edge.salesloft-not-connected if Salesloft is not connected for the tenant (or its connection expired) see: integration-salesloft-add-to-cadence

integration-slack-set-mappings

ChatGPT
Replaces the ENTIRE set of Chili Piper ↔ Slack user mappings for the tenant with the submitted map. This is a strict full-replace, not a merge: any CP user NOT present in the map is left unmapped, and an empty map clears ALL Slack mappings for the tenant. Slack-only; use integration-salesforce-set-mappings / integration-hubspot-set-mappings for the CRMs. - body: a JSON object keyed by Chili Piper user id, whose value is the Slack user id to map that CP user to (e.g. {"<cpUserId>": "U01ABC2DE3F"}); send {} to clear all mappings Note: only the bare Slack user id is required — edge validates it against the tenant's Slack users and fills in email and display name ⚠ atomic: if ANY submitted Slack user id is not a real Slack user for the tenant, the WHOLE request is rejected (422) and nothing is changed ⚠ every key must be a Chili Piper user id of this tenant (look up ids with user-find); any unknown key → 422 edge.integration-mapping-unknown-users listing them, and nothing is changed → {UserId: {id, email, name}} Note: response echoes the resulting full mapping set (the same shape as integration-slack-users), so the caller sees exactly what is now mapped see: integration-slack-users (read current mappings)

integration-slack-users

ChatGPT
Resolves the given Chili Piper user ids to their mapped Slack users — the linked Slack user id plus best-effort email and display name. Slack-only; use integration-salesforce-users / integration-hubspot-users for the CRMs. - userIds: Chili Piper user ids to resolve → {UserId: {id, email, name}} Note: response is a JSON object keyed by Chili Piper user id; unmapped users are omitted see: integration-find-users (CRM-agnostic mapping status), integration-slack-set-mappings (replace mappings)

integration-teams-set-mappings

ChatGPT
Replaces the ENTIRE set of Chili Piper ↔ Microsoft Teams user mappings for the tenant with the submitted map. This is a strict full-replace, not a merge: any CP user NOT present in the map is left unmapped, and an empty map clears ALL Teams mappings for the tenant. Teams-only; use integration-salesforce-set-mappings / integration-hubspot-set-mappings for the CRMs. - body: a JSON object keyed by Chili Piper user id, whose value is the Entra object id (aadObjectId) of the Teams user to map that CP user to (e.g. {"<cpUserId>": "8b0f1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d"}); send {} to clear all mappings Note: only the bare Entra object id is required — edge validates it against the tenant's Microsoft directory and fills in email and display name ⚠ atomic: if ANY submitted object id is not a real Microsoft directory user, the WHOLE request is rejected (422) and nothing is changed ⚠ every key must be a Chili Piper user id of this tenant (look up ids with user-find); any unknown key → 422 edge.integration-mapping-unknown-users listing them, and nothing is changed → {UserId: {id, email, name}} Note: response echoes the resulting full mapping set (the same shape as integration-teams-users), so the caller sees exactly what is now mapped see: integration-teams-users (read current mappings)

integration-teams-users

ChatGPT
Resolves the given Chili Piper user ids to their mapped Microsoft Teams users — the linked Entra object id (aadObjectId) plus best-effort email and display name. Teams-only; use integration-salesforce-users / integration-hubspot-users for the CRMs. - userIds: Chili Piper user ids to resolve → {UserId: {id, email, name}} Note: response is a JSON object keyed by Chili Piper user id; unmapped users are omitted see: integration-find-users (CRM-agnostic mapping status), integration-teams-set-mappings (replace mappings)

knowledge-base-delete

ChatGPT
Deletes a knowledge base entry and its stored content. Chat AI stops using it immediately. Deleting an entry that does not exist succeeds. → {} see: knowledge-base-list (find id)

knowledge-base-file-upload

ChatGPT
Adds a text file to a workspace's Chat AI knowledge base, so Chat AI can answer visitors from it. Send the full file text as content. Accepted formats: Markdown (.md), plain text (.txt), HTML (.html) and CSV (.csv); the format is taken from mediaType, else from the fileName extension. Binary formats (PDF, Word, PowerPoint) are not accepted here — convert them to Markdown first. The whole request must stay under 128 KB (about 120 KB of text); split a larger file into several uploads. Each upload creates a new entry; to replace a file, upload the new version and delete the old entry. Processing is asynchronous: the entry starts as Processing; poll knowledge-base-get until it is Ready or Failed. Note: the entry is attributed to the calling user; with an API key, to the user who created the key, else to a workspace admin. → {id, title, sourceType, status, failureReason, fileName, mediaType, fileSizeBytes, urls, createdAt, createdBy, updatedAt, updatedBy} see: knowledge-base-get (poll status), knowledge-base-list (find duplicates), workspace-list (find workspaceId)

knowledge-base-get

ChatGPT
Reads one knowledge base entry. Poll it after knowledge-base-file-upload until status is no longer Processing. → {id, title, sourceType, status, failureReason, fileName, mediaType, fileSizeBytes, urls, createdAt, createdBy, updatedAt, updatedBy} see: knowledge-base-list (find id)

knowledge-base-list

ChatGPT
Lists the entries of a workspace's Chat AI knowledge base — the files and web pages Chat AI answers from. Each entry reports its source and processing status; only Ready entries are searchable. Pages start at 0; pageSize defaults to 10. → {results: [{id, title, sourceType, status, failureReason, fileName, mediaType, fileSizeBytes, urls, createdAt, createdBy, updatedAt, updatedBy}], total, page, pageSize} Note: sourceType is one of File | Link | NestedLinks | WorkspaceSummary; status is one of Processing | Ready | Failed see: workspace-list (find workspaceId)

meeting-activity

ChatGPT
Returns the admin-UI deep link for a meeting's activity page — a URL you can open to inspect the meeting in Chili Piper reporting. → {url: "https://{subdomain}.chilipiper.{tld}/fire/admin/workspaces/{workspaceId}/reporting/meetings-activity/{meetingId}"} see: meeting-get (find a meetingId), crm-activity (activity link by CRM id instead)

meeting-cancel-post

ChatGPT
Permanently cancels a meeting (v2 POST variant). Preferred for programmatic API consumers over the v1 GET cancel — same effect, but uses POST semantics: no redirect parameters, returns the updated meeting record on success. → {meetingId, meetingStatus: "CANCELLED", ...} ⚠ irreversible; may email attendees see: meeting-get (verify status before/after), meeting-cancel (v1 GET variant for browser redirect flows), crm-cancel-post (cancel by CRM id instead)

meeting-create

ChatGPT
Books a meeting directly — no scheduling-link-init / handoff-init session needed. One host user, one of that host's own personal meeting types, a start time and a primary guest. Sends calendar invites and confirmations like any other booking. - hostUserId (req): the single host; get it from user-find. - meetingTypeId (req): a personal meeting type owned by hostUserId (personal-meeting-type-list with workspaceId = the host's personalWorkspaceId from user-find). Team/admin meeting types are rejected. - startTime (req): ISO-8601. Must be a free slot for the host (check with availability-slots-v2); duration comes from the meeting type. - guestEmail (req), guestFirstName / guestLastName (opt): the primary guest. Names fill the guest form's first/last name fields; personal meeting types require them by default. - additionalGuests (opt): extra invitees, [{email, required?}]. - formData (opt): other guest form answers keyed by data-field reference. - location (opt): overrides the meeting type's default location. - force (opt, default false, HTTP/CLI only): true books over busy time, bypassing ALL availability checks. Needs the scheduling-links.force-schedule permission on the API key. Not available over MCP: the MCP tool always books availability-checked. → Success{meetingId} or Failure{reason} Note: an unavailable slot returns Failure with the reason, not an error. Note: meeting-get can report the new meetingId as not found for a few seconds after Success; retry, do not book again. ⚠ Limits: single host and personal meeting types only. For admin one-on-one, round-robin, group or ownership meeting types use scheduling-link-init + scheduling-link-schedule. Invites are sent immediately — to undo, call meeting-cancel-post. see: availability-slots-v2 (find free times), personal-meeting-type-list (meetingTypeId), user-find (hostUserId), meeting-get (view the booked meeting)

meeting-export-v2-put

ChatGPT
Exports meetings in a time range as CSV via the streaming endpoint — the preferred export for large result sets. Filters travel in the request body: workspace, status, meeting type, and the people involved (assignee, host, or booker user ids). The window may not exceed 7 days. → {filename: "meetings-export-....csv", data: "<CSV content>"} see: meeting-list-put (JSON instead of CSV), user-find (resolve an email to a userId for the people filters)

meeting-get

ChatGPT
Fetches the full record for a single meeting. Read fields defensively — the payload shape may evolve, and a few names are surprising: the status is meetingStatus (not "status"), the identifier is id (not "meetingId"), the scheduled time is a ScheduledAt entry inside the activities array (there is no top-level scheduledAt), and there are no top-level linkedCrmEventIds. → meeting object see: meeting-list (find a meetingId by time range), meeting-cancel / meeting-noshow (change status), meeting-activity (admin deep link), crm-get (fetch by CRM id instead)

meeting-get-meeting-prep

ChatGPT
Fetches the AI-generated meeting prep brief for a given meeting. The brief is generated asynchronously before the meeting; this endpoint returns the current state of that generation. → {status: "InProgress"|"Ready"|"Failed"|"Skipped"|"Cancelled", content: "<brief text>", reason: "<skip/fail/cancel reason>", inviteTitle: "<calendar event title>"} Note: content is present only when status is Ready. reason is present for Failed, Skipped, and Cancelled. inviteTitle is the calendar event title used when the brief was delivered. see: meeting-get (fetch the meeting record)

meeting-list-put

ChatGPT
Returns meetings within a time range (paginated). Prefer this over the GET form: filters travel in the request body, so long workspace or status lists don't hit URL-length limits. Narrow by workspace and by status (Active, Canceled, NoShow, Completed). The window may not exceed 7 days. → {results: [{meetingId, status, scheduledAt, attendees, assignedUserId, workspaceId}], pagination} see: meeting-export-v2-put (CSV export with richer host/assignee/booker filters), meeting-get (full record)

meeting-noshow-post

ChatGPT
Marks a meeting as a no-show (v2 POST variant). Preferred for programmatic API consumers over the v1 GET noshow — same effect, but uses POST semantics: no redirect parameters, returns the updated meeting record on success. Status becomes NO_SHOW and can update the CRM record and trigger no-show automations. → {meetingId, meetingStatus: "NO_SHOW", ...} ⚠ not reversible via API (admin UI only); may trigger CRM/notification workflows see: meeting-get (verify before/after), meeting-noshow (v1 GET variant for browser redirect flows), crm-noshow-post (mark no-show by CRM id instead)

meeting-patch

ChatGPT
Reschedules or edits a booked meeting. Every field is optional — send only what you want to change. Setting startTime reschedules it (availability is re-checked and the new slot reserved before the change applies). assignees and additionalGuests are full replacements, not merges: send the complete desired list, [] to clear it, or omit to leave it untouched (additionalGuests only affects external guests, never internal attendees or the primary guest). primaryGuestAttendance controls the primary guest's attendee entry — Required/Optional set them as a required/optional attendee, Removed drops them from the meeting; omit to leave their entry unchanged (this only changes the attendee set, never the primaryGuest record). → updated meeting object ⚠ triggers calendar event updates, CRM activity updates, distribution counter adjustments, and notification emails see: meeting-get (verify before/after), availability-slots (check open times before rescheduling)

meeting-prep-brief-delete

ChatGPT
Deletes a brief. All its meeting types stop getting Meeting Prep briefs. To disable Meeting Prep for only some meeting types, remove them from meetingTypeIds with meeting-prep-brief-upsert instead. → {} Note: an unknown brief id (or one in another workspace) returns a 404. ⚠ irreversible via API see: meeting-prep-brief-list (confirm the briefId and workspaceId before deleting)

meeting-prep-brief-get

ChatGPT
Fetches one Meeting Prep brief by id. → {id, workspaceId, name, description?, inviteTitle?, prompt, meetingTypeIds: [id], webSearch: bool, crm: {type: "NoCrm"} | {type: "Salesforce", lead, contact, account, relatedOpportunities, relatedCases} | {type: "Hubspot", contact, company, relatedDeals, relatedTickets}, enrichmentProviders: ["LeadIQ"|"Apollo"|"ZoomInfo"|"Lusha"], clayTables: [{id, description}], guestDataScope: "PrimaryOnly"|"AllExternalGuests", recipients: "AllInternalAttendees"|"AssigneeIfPresentElseHost", deliveryChannels: ["CalendarEvent"|"Slack"], generationTiming: "WhenBooked"|"BeforeMeeting", generationLeadTime?, updatedAt} Note: an unknown brief id (or one in another workspace) returns a 404. see: meeting-prep-brief-list (find the briefId and workspaceId)

meeting-prep-brief-list

ChatGPT
Lists Meeting Prep briefs. A brief is an AI prompt that writes a pre-meeting summary for the meeting types in its meetingTypeIds. Omit workspaceId to list across every workspace; pass name to filter by name. → {id, workspaceId, name, description?, inviteTitle?, prompt, meetingTypeIds: [id], webSearch: bool, crm: {type: "NoCrm"} | {type: "Salesforce", lead, contact, account, relatedOpportunities, relatedCases} | {type: "Hubspot", contact, company, relatedDeals, relatedTickets}, enrichmentProviders: ["LeadIQ"|"Apollo"|"ZoomInfo"|"Lusha"], clayTables: [{id, description}], guestDataScope: "PrimaryOnly"|"AllExternalGuests", recipients: "AllInternalAttendees"|"AssigneeIfPresentElseHost", deliveryChannels: ["CalendarEvent"|"Slack"], generationTiming: "WhenBooked"|"BeforeMeeting", generationLeadTime?, updatedAt} see: meeting-prep-brief-meeting-type-usages (which meeting types have Meeting Prep enabled), meeting-prep-brief-upsert (create or change a brief)

meeting-prep-brief-meeting-type-usages

ChatGPT
Lists every meeting type that has Meeting Prep enabled and the brief that serves it. A meeting type that is not in this list has Meeting Prep disabled. Omit workspaceId to list across every workspace. → [{workspaceId, meetingTypeId, briefId, briefName}] see: meeting-prep-brief-upsert (enable or disable Meeting Prep for a meeting type), meeting-type-list (all meeting types)

meeting-prep-brief-upsert

ChatGPT
Creates a brief (omit briefId) or updates one (pass briefId). On update, send only the fields to change; omitted fields keep their value. On create, name and prompt are required; defaults: no meeting types, webSearch true, crm NoCrm, no enrichment, no Clay, guestDataScope PrimaryOnly, recipients AssigneeIfPresentElseHost, deliveryChannels [CalendarEvent], generationTiming WhenBooked. - Enable Meeting Prep for a meeting type: add its id to meetingTypeIds (send the full list; it replaces the current one). Disable it: remove the id. A meeting type can be in at most one brief; remove it from the other brief first, else 400. - generationTiming "BeforeMeeting" needs generationLeadTime (e.g. "2 hours"); "WhenBooked" ignores it. - description / inviteTitle: an empty string clears the value. → {id, workspaceId, name, description?, inviteTitle?, prompt, meetingTypeIds: [id], webSearch: bool, crm: {type: "NoCrm"} | {type: "Salesforce", lead, contact, account, relatedOpportunities, relatedCases} | {type: "Hubspot", contact, company, relatedDeals, relatedTickets}, enrichmentProviders: ["LeadIQ"|"Apollo"|"ZoomInfo"|"Lusha"], clayTables: [{id, description}], guestDataScope: "PrimaryOnly"|"AllExternalGuests", recipients: "AllInternalAttendees"|"AssigneeIfPresentElseHost", deliveryChannels: ["CalendarEvent"|"Slack"], generationTiming: "WhenBooked"|"BeforeMeeting", generationLeadTime?, updatedAt} Note: meeting types must belong to the brief's workspace. Briefs run the AI per booked meeting, which uses Meeting Prep credits. ⚠ takes effect for new bookings immediately see: meeting-prep-brief-get (read before editing), meeting-prep-brief-meeting-type-usages (check which brief has a meeting type), meeting-type-list (find meeting type ids)

meeting-type-attach-reminder

ChatGPT
Links an existing reminder to a team meeting type so it starts sending it, and returns the updated meeting type. The reminder must live in the same workspace (create one with meeting-type-reminder-create). Idempotent: re-attaching an already-attached reminder is a no-op. → {id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}} ⚠ takes effect immediately see: meeting-type-reminder-create (make a reminder first), meeting-type-get (confirm the association), meeting-type-detach-reminder (remove it)

meeting-type-create

ChatGPT
Creates a reusable team meeting type; the backend fills in product defaults for anything you omit. Only name, duration ("30 minutes") and the default location are set by the create call itself — description, inviteTitle, inviteDescription, location alternatives, availability, schedule, assigneesMeetingLimit, and the admin scheduling link that carries sharedWith are applied by FURTHER calls immediately after. description is internal (never shown to guests); to set the guest-facing calendar invite use inviteTitle/inviteDescription, both of which accept {!CP.Object.Field} merge tags (e.g. {!CP.DataField.FirstName}). sharedWith is {type: "Workspace"} or {type: "Teams", teamIds}; supplying it provisions the admin (one-on-one) scheduling link that carries the scope — omit it and sharing can only be set later by managing that link directly. Note: if a follow-up call fails, the new meeting type is deleted (rolled back) and the original error is returned, so you can retry safely. If the rollback also fails, or the meeting type came from an earlier call with the same idempotencyKey (never deleted), the error is edge.meeting-type-create-incomplete: it names the meeting type id and the failed step — delete it with meeting-type-delete, fix it with meeting-type-update, or retry with the same idempotencyKey. - location (opt): {default: <Location>, others: [<Location>]}; omit to keep the workspace default. Each <Location> is one of: {type: "ZoomOneTimeLink"|"TeamsMeetingLink"|"GongLink"|"GoToMeetingLink"|"RingCentralLink"|"WebexLink"|"HostsDefaultConferenceDetails"|"HostsDefaultPhysicalLocation", index, host: "Host"|"AssigneeIfExists"|"BookerIfExists"} {type: "CalendarPlatformConference", index} {type: "AskTheGuest", index} {type: "DefinedInMeetingType", index, value?} - availability (opt): {minimumNotice?, bookingWindow?, slotIncrement?: {type: "Dynamic"}|{type: "Fixed", every}}; durations like "24 hours" or "PT72H"; omitted sub-fields keep the product default. - schedule (opt): {type: "HostWorkingHours"} (each host's own working hours, the default) or {type: "Fixed", workingHours: {monday: [{start: "09:00", end: "17:00"}], ...}, timezone?, forceGuestTimezone?, activeFrom?, activeTo?}; a weekday left empty is not bookable; end "00:00" means midnight; timezone null means each host's timezone. - assigneesMeetingLimit (opt): caps meetings of this type per assignee — {type: "NoMeetingLimit"} or {type: "SetMeetingLimit", numberOfMeetings, reset}; reset is one of: {type: "Daily", every, startsOn, resetLocalTime: "00:00", timezone} {type: "Weekly", every, startsOn, resetDayOfWeek: "MONDAY".."SUNDAY", timezone} {type: "Monthly", every, startsOn, resetDay: 1..28, timezone} Note: every is the period length (1 = every day/week/month); startsOn (a date) anchors multi-period cycles. - idempotencyKey (opt): a UUID (a MeetingTypeId) the caller generates per logical create — a retry with the same value returns the already-created meeting type instead of duplicating it; reusing the same value with a DIFFERENT payload fails with 409. → {id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}} see: meeting-type-list (find a workspaceId), meeting-type…

meeting-type-delete

ChatGPT
Deletes a team meeting type by id. ⚠ irreversible via API; any scheduling links or routers referencing this meeting type stop working see: meeting-type-list or meeting-type-get (confirm the id before deleting)

meeting-type-detach-reminder

ChatGPT
Unlinks a reminder from a team meeting type (it stops sending it), and returns the updated meeting type. The reminder itself is NOT deleted — it stays available to re-attach or to use elsewhere; delete it entirely with meeting-type-reminder-delete. Idempotent: detaching a reminder that is not attached is a no-op. → {id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}} ⚠ takes effect immediately see: meeting-type-get (confirm), meeting-type-attach-reminder (re-attach), meeting-type-reminder-delete (delete the reminder entirely)

meeting-type-folder-create

ChatGPT
Creates a meeting type folder (asset group) in the workspace. The folder starts empty; assign meeting type items to it with meeting-type-folder-move / meeting-type-folder-move-bulk. → {id, name, displayOrder, itemCount: 0, revision} see: meeting-type-folder-move (assign a meeting type to the new folder), meeting-type-folder-list (list folders)

meeting-type-folder-delete

ChatGPT
Deletes a meeting type folder. Member meeting type items are not deleted — they are detached (moved to ungrouped). Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via meeting-type-folder-list right before calling). → no payload on success ⚠ revision conflict = re-fetch via meeting-type-folder-list and retry see: meeting-type-folder-list (get a fresh folderId+revision immediately before calling)

meeting-type-folder-list

ChatGPT
Lists the meeting type folders (asset groups) in a workspace, ordered by display order. Use this to discover folderIds and grab a fresh revision before renaming or deleting a folder. → {items: [{id, name, displayOrder, itemCount, revision}]} ⚠ call immediately before meeting-type-folder-rename / meeting-type-folder-delete to get a fresh revision — a stale revision causes a conflict error see: meeting-type-folder-create (add a folder), meeting-type-folder-move (assign a meeting type to a folder)

meeting-type-folder-move

ChatGPT
Assigns one meeting type to a folder, or clears its folder membership when folderId is omitted (null) — last-write-wins. → no payload on success see: meeting-type-folder-list (find a folderId), meeting-type-folder-move-bulk (assign many meeting type items at once)

meeting-type-folder-move-bulk

ChatGPT
Assigns multiple meeting type items to a folder, or clears their folder membership when folderId is omitted (null), in one request — last-write-wins. → no payload on success see: meeting-type-folder-list (find a folderId), meeting-type-folder-move (assign a single meeting type)

meeting-type-folder-rename

ChatGPT
Renames a meeting type folder. Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via meeting-type-folder-list right before calling). → {id, name, displayOrder, itemCount, revision} ⚠ revision conflict = re-fetch via meeting-type-folder-list and retry see: meeting-type-folder-list (get a fresh folderId+revision immediately before calling)

meeting-type-get

ChatGPT
Fetches one team meeting type by id, including its attached reminders (unlike meeting-type-list, which leaves reminders null). Use it before editing to read current state, or once you already know the id instead of browsing the list. → {id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}} Note: meetingLimit.timeframe is approximate — the backend's reset schedule (e.g. every-2-weeks) is summarised to the nearest timeframe; finer reset details (every-N, anchor day) are not surfaced. Note: sharedWith (the sharing scope: whole workspace vs specific teams) is read from the meeting type's admin (one-on-one) scheduling link; it is null when no such link, or more than one, is found — null means "indeterminate", not "workspace". ⚠ personal meeting types are not manageable here and return an error see: meeting-type-list (browse to find an id), meeting-type-update (edit), meeting-type-delete (remove)

meeting-type-list

ChatGPT
Browses the tenant's reusable team meeting types — the templates that scheduling links and routers reference. Personal meeting types are excluded. Omit workspaceId to fan out across every workspace; pass it to scope to one. → [{id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders?: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}}] Note: reminders are not fetched on list (the field is null) — use meeting-type-get to read a meeting type's reminders; null means "not fetched", never "no reminders". Note: meetingLimit.timeframe is approximate — the backend's reset schedule (e.g. every-2-weeks) is summarised to the nearest timeframe; finer reset details (every-N, anchor day) are not surfaced. Note: sharedWith (the sharing scope: whole workspace vs specific teams) is read from the meeting type's admin (one-on-one) scheduling link; it is null when no such link, or more than one, is found — null means "indeterminate", not "workspace". see: meeting-type-get (fetch a single meeting type), meeting-type-create (add one), meeting-type-update (edit one)

meeting-type-reminder-create

ChatGPT
Creates a reminder in a workspace; attach it to a meeting type afterwards with meeting-type-attach-reminder. trigger is {kind, offset}: kind is "BeforeMeeting"/"BeforeMeetingNoResponse"/"AfterMeeting" and offset (e.g. "1 hour") is required. title is the email subject (Email only; ignored for Sms). sendTo (recipients) and sendFrom (sender identity) are Email-only; smsSendFrom (sender number) is Sms-only — each defaults to the backend value (SendToAllGuests / SendFromHost / AnyNumber) when omitted. → {id, workspaceId, channel: "Email"|"Sms", trigger: {kind, offset?}, name, title?, body, sendTo?: "SendToAllGuests"|"SendToMainGuestOnly", sendFrom?: "SendFromHost"|"SendFromBooker"|"SendFromNoReply", smsSendFrom?: "AnyNumber"|"LocalAreaNumber"} Note: other advanced send-restrictions are backend-defaulted and not settable here. see: meeting-type-reminder-list (find a workspaceId), meeting-type-reminder-update (edit afterwards)

meeting-type-reminder-delete

ChatGPT
Deletes a reminder entirely. To stop one meeting type sending it while keeping the reminder, use meeting-type-detach-reminder instead. → {} Note: an unknown reminder id (or one in another workspace) returns a typed 404, not a silent success — the id is verified in the workspace before deletion. ⚠ irreversible via API; any meeting types referencing this reminder stop sending it see: meeting-type-reminder-list (confirm the reminderId and workspaceId before deleting)

meeting-type-reminder-list

ChatGPT
Browses the tenant's reminders — workspace-scoped Email/Sms notifications that meeting types attach to fire before or after the meeting. Omit workspaceId to fan out across every workspace; pass it to scope to one. → [{id, workspaceId, channel: "Email"|"Sms", trigger: {kind: "BeforeMeeting"|"BeforeMeetingNoResponse"|"MeetingBooked"|"AfterMeeting", offset?}, name, title?, body, sendTo?: "SendToAllGuests"|"SendToMainGuestOnly", sendFrom?: "SendFromHost"|"SendFromBooker"|"SendFromNoReply", smsSendFrom?: "AnyNumber"|"LocalAreaNumber"}] Note: sendTo/sendFrom are Email-only; smsSendFrom is Sms-only. Other advanced send-restrictions are managed by the backend and not exposed. Note: "MeetingBooked" is a legacy read-only kind — such reminders are never sent and it cannot be set on create/update. see: meeting-type-reminder-create (add one), meeting-type-reminder-update (edit one), meeting-type-reminder-delete (remove one)

meeting-type-reminder-update

ChatGPT
Edits a reminder in place — attached meeting types pick up the change automatically. Send only the fields you want to change; the channel is fixed and cannot switch between Email and Sms. trigger is {kind, offset} (both required; omit trigger to keep the current one). sendTo/sendFrom patch the Email recipients/sender identity; smsSendFrom patches the Sms sender number — omit to keep the current value; fields for the other channel are ignored. → {id, workspaceId, channel: "Email"|"Sms", trigger: {kind, offset?}, name, title?, body, sendTo?: "SendToAllGuests"|"SendToMainGuestOnly", sendFrom?: "SendFromHost"|"SendFromBooker"|"SendFromNoReply", smsSendFrom?: "AnyNumber"|"LocalAreaNumber"} Note: other advanced send-restrictions are preserved unchanged. Note: a legacy reminder with trigger kind "MeetingBooked" cannot be updated as is (400). Change its trigger to a timed kind ("BeforeMeeting"/"BeforeMeetingNoResponse"/"AfterMeeting") in the same update, or delete it. ⚠ takes effect immediately see: meeting-type-reminder-list (find the reminderId and workspaceId before editing)

meeting-type-update

ChatGPT
Edits a team meeting type. Every field is optional — send only what you want to change; omitted fields keep their current value. description is internal; change the guest-facing invite via inviteTitle/inviteDescription ({!CP.Object.Field} merge tags, e.g. {!CP.DataField.FirstName}). location is a full replacement of the conferencing config, not a merge. meetingLimit caps bookings per guest (limitBy) per timeframe; assigneesMeetingLimit caps meetings per assignee. Prefer reading current state with meeting-type-get first. - status (opt): "Active" or "Inactive" - location (opt): replaces the full config — {default: <Location>, others: [<Location>]}; omit to leave unchanged. Each <Location> is one of: {type: "ZoomOneTimeLink"|"TeamsMeetingLink"|"GongLink"|"GoToMeetingLink"|"RingCentralLink"|"WebexLink"|"HostsDefaultConferenceDetails"|"HostsDefaultPhysicalLocation", index, host: "Host"|"AssigneeIfExists"|"BookerIfExists"} {type: "CalendarPlatformConference", index} {type: "AskTheGuest", index} {type: "DefinedInMeetingType", index, value?} - meetingLimit (opt): {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count} - availability (opt): {minimumNotice?, bookingWindow?, slotIncrement?: {type: "Dynamic"}|{type: "Fixed", every}}; durations like "24 hours" or "PT72H"; omitted sub-fields keep their current value. Note: minimumNotice is a plain duration from now — it does not skip weekends or holidays. - schedule (opt): full replacement — {type: "HostWorkingHours"} (each host's own working hours) or {type: "Fixed", workingHours: {monday: [{start: "09:00", end: "17:00"}], ...}, timezone?, forceGuestTimezone?, activeFrom?, activeTo?}; a weekday left empty is not bookable; end "00:00" means midnight; timezone null means each host's timezone. - assigneesMeetingLimit (opt): caps meetings of this type per assignee — {type: "NoMeetingLimit"} or {type: "SetMeetingLimit", numberOfMeetings, reset}; reset is one of: {type: "Daily", every, startsOn, resetLocalTime: "00:00", timezone} {type: "Weekly", every, startsOn, resetDayOfWeek: "MONDAY".."SUNDAY", timezone} {type: "Monthly", every, startsOn, resetDay: 1..28, timezone} Note: every is the period length (1 = every day/week/month); startsOn (a date) anchors multi-period cycles. - sharedWith (opt): new scope — {type: "Workspace"} or {type: "Teams", teamIds}; applied to the admin (one-on-one) scheduling link, which must exist as exactly one (409 if none — set sharedWith at create time first — or more than one). Note: sharedWith is applied AFTER the other changes commit — a 409 here means those are already persisted (re-run without sharedWith, then fix the admin link directly). → {id, workspaceId, name, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, sharedWith?: {type: "Workspace"}|{type: "Teams", teamIds}, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}], availability?: {minimumNotice, bookingWindow, slotIncrement: {type: "Dynamic"}|{type: "Fixed", every}}, schedule?: {type: "HostWorkingHours"}|{type: "Fixed", workingHours: {monday..sunday: [{start, end}]}, timezone?, forceGuestTimezone, activeFrom?, activeTo?}, assigneesMeetingLimit?: {type: "NoMeetingLimit"}|{type: "SetMeetingLimit", numberOfMeetings, reset: {type: "Daily"|"Weekly"|"Monthly", every, startsOn, timezone, resetLocalTime?, resetDayOfWeek?, resetDay?}}} ⚠ takes effect immediately ⚠ this publishes directly — it does not edit a draft; the web app's separate draft/publish flow is bypassed, so any unpublished draft edits for this meeting type in the web app are discarded (the meeting type's live state becomes exactly what you set here) see: meeting-type-get (fetch current state before editing)

personal-meeting-type-create

ChatGPT
Creates a personal meeting type (a booking page) in ownerUserId's personal workspace; the backend fills in product defaults and generates the booking slug. Only name, duration ("30 minutes"), the default location and syncToCrm are set by the create call itself — description, inviteTitle, inviteDescription and location alternatives are applied by a FURTHER call immediately after. description is internal; the guest-facing invite is inviteTitle/inviteDescription ({!CP.Object.Field} merge tags, e.g. {!CP.DataField.FirstName}). Note: if the follow-up call fails, the new meeting type is deleted (rolled back) and the original error is returned, so you can retry safely. If the rollback also fails, or the meeting type came from an earlier call with the same idempotencyKey (never deleted), the error is edge.meeting-type-create-incomplete: it names the meeting type id and the failed step — delete it with personal-meeting-type-delete, fix it with personal-meeting-type-update, or retry with the same idempotencyKey. - ownerUserId (req): the user in whose personal workspace the meeting type is created; must be an existing user of this tenant (unknown id → 404, nothing is created). - location (opt): {default: <Location>, others: [<Location>]}. Each <Location> is one of: {type: "ZoomOneTimeLink"|"TeamsMeetingLink"|"GongLink"|"GoToMeetingLink"|"RingCentralLink"|"WebexLink"|"HostsDefaultConferenceDetails"|"HostsDefaultPhysicalLocation", index, host: "Host"|"AssigneeIfExists"|"BookerIfExists"} {type: "CalendarPlatformConference", index} {type: "AskTheGuest", index} {type: "DefinedInMeetingType", index, value?} - idempotencyKey (opt): a UUID (a MeetingTypeId) the caller generates per logical create — a retry with the same value returns the already-created meeting type; reusing it with a DIFFERENT payload fails with 409. → {id, workspaceId, name, slug, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, syncToCrm, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}]} see: personal-meeting-type-update (edit afterwards)

personal-meeting-type-delete

ChatGPT
Deletes a personal meeting type by id. ⚠ irreversible via API; the owner's booking page for this meeting type stops working see: personal-meeting-type-list or personal-meeting-type-get (confirm the id before deleting)

personal-meeting-type-get

ChatGPT
Fetches one personal meeting type by id, including its attached reminders (unlike personal-meeting-type-list, which leaves reminders null). Use it before editing to read current state. → {id, workspaceId, name, slug, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, syncToCrm, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}]} ⚠ team meeting types are not manageable here and return an error — use meeting-type-get for those see: personal-meeting-type-list (browse to find an id), personal-meeting-type-update (edit), personal-meeting-type-delete (remove)

personal-meeting-type-list

ChatGPT
Browses PERSONAL meeting types in the tenant — the booking pages individual users created for themselves, across all users' personal workspaces. This is the counterpart of meeting-type-list (which covers team meeting types only). Org-wide by default; optionally scope to a single personal workspace with workspaceId (each personal booking page lives in its owner's personal workspace — see the workspaceId field on every result). Paginated: page through with page/pageSize. Use it to audit or bulk-edit personal booking pages (e.g. force everyone's location to Gong via personal-meeting-type-update). - workspaceId (opt): return only this personal workspace's meeting types. → {results: [{id, workspaceId, name, slug, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, syncToCrm, reminders?}], total, page, pageSize} Note: reminders are not fetched on list (the field is null) — use personal-meeting-type-get to read them; null means "not fetched", never "no reminders". Note: workspaceId is the owner's personal workspace. see: personal-meeting-type-get (fetch one), personal-meeting-type-update (edit one, e.g. change location)

personal-meeting-type-update

ChatGPT
Edits a personal meeting type. Every field is optional — send only what you want to change; omitted fields keep their current value. Personal-only config (slug, guest form, password, host meeting limit, force-guest-timezone) is preserved unchanged. location is a full replacement of the conferencing config, not a merge. Common use: force the location to Gong across many users' personal booking pages. - status (opt): "Active" or "Inactive" - location (opt): replaces the full config — {default: <Location>, others: [<Location>]}. Each <Location> is one of: {type: "ZoomOneTimeLink"|"TeamsMeetingLink"|"GongLink"|"GoToMeetingLink"|"RingCentralLink"|"WebexLink"|"HostsDefaultConferenceDetails"|"HostsDefaultPhysicalLocation", index, host: "Host"|"AssigneeIfExists"|"BookerIfExists"} {type: "CalendarPlatformConference", index} {type: "AskTheGuest", index} {type: "DefinedInMeetingType", index, value?} - meetingLimit (opt): {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count} - syncToCrm (opt): enable/disable CRM sync. → {id, workspaceId, name, slug, description?, inviteTitle, inviteDescription, status, isActive, duration, location: {default: {type, index, host?, value?}, others: [{type, index, host?, value?}]}, buffers: {before?, after?}, meetingLimit?: {limitBy: "Email"|"Domain", timeframe: "Hourly"|"Daily"|"Weekly"|"Monthly"|"Yearly", count}, syncToCrm, reminders: [{id, workspaceId, channel, trigger: {kind, offset?}, name, title?, body, sendTo?, sendFrom?, smsSendFrom?}]} ⚠ takes effect immediately ⚠ this publishes directly — it does not edit a draft; any unpublished draft edits for this meeting type in the web app are discarded (the live state becomes exactly what you set here) see: personal-meeting-type-get (fetch current state before editing)

resource-scheduler-run

ChatGPT
Triggers a resource scheduler run on demand: executes its configured query and dispatches the matched records into the linked executing flow. → {type: "Triggered", startedAt} on success; {type: "Failed", message} when the trigger could not start ⚠ dispatches real records to the configured flow — calling twice runs and dispatches twice

rule-create

ChatGPT
Creates a reusable routing rule so routers can reference it. It is live immediately (revision=0 — the first revision is 0, not 1). Choose the dto variant matching the rule kind — ownership rules (which resolve a record owner) use CreateOwnershipRuleRequest and must carry a teamId; assignment-table rules (which resolve the assignee from an assignment table on match) use CreateAssignmentTableRuleRequest and carry an assignmentTableId; all others use CreateRuleRequest. Omit ruleId to auto-assign a UUID, or supply one to force it. The fastest safe path is to copy conditions (and workspaceId) from a rule-list result, since those already carry the required discriminators. - dto (req): rule definition — choose ONE variant: Non-ownership: {type: "CreateRuleRequest", workspaceId, name, conditions} Ownership: {type: "CreateOwnershipRuleRequest", workspaceId, name, conditions, teamId} Assignment table: {type: "CreateAssignmentTableRuleRequest", workspaceId, name, assignmentTableId, conditions} - conditions: EVERY conditions object requires a "type" discriminator field. Example structure: {type: "ConditionGroup", id: "<any-string>", operator: "and"|"or", conditions: [ {type: "StaticValueCondition", conditionId: "<any-string>", dataReference: {source: "SF"|"DF"|"CP"|"HS"|"MK", object: "<CrmObject>", field: "<FieldName>"}, operator: ">"|"<"|">="|"<="|"equal"|"notEqual"|"isAnyOf"|"isNotAnyOf"|"contains"|"doesNotContain"| "containsAnyOf"|"containsNoneOf"|"startsWith"|"endsWith"|"startsWithAnyOf"|"endsWithAnyOf", value: <any JSON value>} {type: "SingleParameterCondition", conditionId: "<any-string>", dataReference: {source, object, field}, operator: "notEmpty"|"empty"} ]} - product: not settable. The rules backend sets product to "Distro" on every new rule. It is a legacy label only: it does not limit which product (Distro, Concierge, Handoff, Chat or other Web Experiences) can reference the rule. → {type, id, workspaceId, name, conditions, ruleBuilderVersion, product, metadata: {revision: 0, ...}} ⚠ live immediately with no dry-run — a misconfigured rule may misroute leads in every router that references it see: rule-list (get workspaceId and copy conditions from an existing rule), web-experience-create (a RoutingRule conversation node references a rule by id)

rule-delete

ChatGPT
Soft-deletes a routing rule, removing it from evaluation immediately. Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it right before calling). Traffic that used to match this rule now falls through to the next matching rule, so make sure a fallback exists first. → the deleted rule's final state ⚠ irreversible via API; a revision conflict means re-fetch and retry ⚠ a legacy Default-builder rule is rejected with 400 edge.rule-builder-version-unsupported and is not deleted see: rule-list or rule-get (get fresh ruleId+revision immediately before calling, and verify deletion after)

rule-folder-create

ChatGPT
Creates a rule folder (asset group) in the workspace. The folder starts empty; assign rule items to it with rule-folder-move / rule-folder-move-bulk. → {id, name, displayOrder, itemCount: 0, revision} see: rule-folder-move (assign a rule to the new folder), rule-folder-list (list folders)

rule-folder-delete

ChatGPT
Deletes a rule folder. Member rule items are not deleted — they are detached (moved to ungrouped). Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via rule-folder-list right before calling). → no payload on success ⚠ revision conflict = re-fetch via rule-folder-list and retry see: rule-folder-list (get a fresh folderId+revision immediately before calling)

rule-folder-list

ChatGPT
Lists the rule folders (asset groups) in a workspace, ordered by display order. Use this to discover folderIds and grab a fresh revision before renaming or deleting a folder. → {items: [{id, name, displayOrder, itemCount, revision}]} ⚠ call immediately before rule-folder-rename / rule-folder-delete to get a fresh revision — a stale revision causes a conflict error see: rule-folder-create (add a folder), rule-folder-move (assign a rule to a folder)

rule-folder-move

ChatGPT
Assigns one rule to a folder, or clears its folder membership when folderId is omitted (null) — last-write-wins. → no payload on success see: rule-folder-list (find a folderId), rule-folder-move-bulk (assign many rule items at once)

rule-folder-move-bulk

ChatGPT
Assigns multiple rule items to a folder, or clears their folder membership when folderId is omitted (null), in one request — last-write-wins. → no payload on success see: rule-folder-list (find a folderId), rule-folder-move (assign a single rule)

rule-folder-rename

ChatGPT
Renames a rule folder. Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it via rule-folder-list right before calling). → {id, name, displayOrder, itemCount, revision} ⚠ revision conflict = re-fetch via rule-folder-list and retry see: rule-folder-list (get a fresh folderId+revision immediately before calling)

rule-get

ChatGPT
Fetches one routing rule by ID. Use this when you already know the ruleId; use rule-list to browse. Rules are reusable routing conditions that routers reference — editing a rule affects every router that uses it. → {id, workspaceId, name, conditions, ruleBuilderVersion, product, metadata} ⚠ a legacy Default-builder rule returns 400 edge.rule-builder-version-unsupported (this API cannot represent it) see: rule-list (browse rules to find a ruleId), rule-modify (update rule), rule-delete (remove rule)

rule-list

ChatGPT
Browses the org's reusable routing rules to discover ruleIds, copy condition structures, and grab a fresh revision before editing. Returns only active ExplicitV1-builder rules; there is no ruleBuilderVersion filter. The only other builder version, the legacy implicit "Default" builder, is deprecated and this API cannot represent its rules, so they are always excluded; rule-get, rule-modify and rule-delete reject them with 400 edge.rule-builder-version-unsupported and change nothing (manage them in the app). Rules are the conditions that routers reference for routing decisions. Paging defaults to page 0 / pageSize 200; optionally filter by workspaceId or name. → {results: [{type, id, workspaceId, name, conditions, ruleBuilderVersion, product?, metadata: {revision, ...}, teamId?}], total, page, pageSize} ⚠ call immediately before rule-modify/rule-delete to get a fresh revision — a stale revision causes a conflict error see: rule-get (fetch single rule by id), rule-modify (update rule), rule-delete (remove rule)

rule-modify

ChatGPT
Replaces a rule's name and conditions; the change is live immediately across every router that references the rule. This is a full replace of name+conditions, not a merge. Uses optimistic concurrency: the supplied revision must equal the current one (re-fetch it right before calling — any elapsed time risks a conflict), and the dto variant must match the rule's kind (UpdateOwnershipRuleRequest for ownership rules, UpdateAssignmentTableRuleRequest for assignment-table rules, else UpdateRuleRequest). Safest path is to copy conditions from a rule-list/rule-get result and edit them, since they already carry the required discriminators. - dto (req): new rule definition — choose variant matching the rule type from rule-list: Non-ownership: {type: "UpdateRuleRequest", name, conditions} Ownership: {type: "UpdateOwnershipRuleRequest", name, conditions, teamId} Assignment table: {type: "UpdateAssignmentTableRuleRequest", name, assignmentTableId, conditions} - conditions: EVERY conditions object requires a "type" discriminator field. If constructing from scratch: {type: "ConditionGroup", id: "<any-string>", operator: "and"|"or", conditions: [ {type: "StaticValueCondition", conditionId: "<any-string>", dataReference: {source: "SF"|"DF"|"CP"|"HS"|"MK", object: "<CrmObject>", field: "<FieldName>"}, operator: ">"|"<"|">="|"<="|"equal"|"notEqual"|"isAnyOf"|"isNotAnyOf"|"contains"|"doesNotContain"| "containsAnyOf"|"containsNoneOf"|"startsWith"|"endsWith"|"startsWithAnyOf"|"endsWithAnyOf", value: <any JSON value>} ]} → updated rule with incremented revision ⚠ takes effect immediately; revision conflict = re-fetch via rule-list/rule-get and retry ⚠ a legacy Default-builder rule is rejected with 400 edge.rule-builder-version-unsupported before any change ⚠ this publishes directly — it does not edit a draft; the web app's separate draft/publish flow is bypassed, so any unpublished draft edits for this rule in the web app are discarded (the rule's live state becomes exactly what you set here) see: rule-list or rule-get (get fresh ruleId+revision immediately before calling)

team-add-users

ChatGPT
Adds users to a team so they become routing targets for distributions scoped to it. Already-members are silently skipped. → {id, workspaceId, name, members, metadata} — updated team record see: team-list (find teamId and verify after), user-find (resolve emails to userIds)

team-create

ChatGPT
Creates a team inside a workspace to serve as a routing target for distributions. Optionally seed it with initial members (userIds); add more later with team-add-users. → {id, workspaceId, name, members, metadata} see: workspace-list (resolve workspaceId), user-find (resolve emails to member userIds), team-add-users (add more members later)

team-delete

ChatGPT
Permanently deletes a team. Fails while any active distribution still references it — reassign those with distribution-update-v3 first. Members stay in the workspace; only the team grouping is removed. → {id, workspaceId, name, members, metadata} — the deleted team record ⚠ irreversible; does not remove members from the workspace see: team-list (find teamId), distribution-update-v3 (reassign before deleting)

team-list-put

ChatGPT
Lists teams in the org and resolves the teamId that add/remove-users and distribution calls reference. Filters go in the request body (avoiding query-string length limits): workspaceIds, name (substring match), and member (teams containing all given userIds). Defaults to pageSize 200. → {results: [{teamId, name, members}]} see: user-find (resolve email → userId for the member filter)

team-remove-users

ChatGPT
Removes users from a single team; their other team memberships are untouched. Non-members are silently skipped. To clear a user from every team at once, use team-remove-users-all instead. → {id, workspaceId, name, members, metadata} — updated team record ⚠ may affect active distributions scoped to this team see: team-list (find teamId and verify after), team-remove-users-all (remove from all teams at once)

team-remove-users-all

ChatGPT
Clears users from every team at once, for offboarding. Pass workspaceIds to limit the sweep to teams in those workspaces; omit for org-wide. Workspace memberships are left intact — use workspace-remove-users-all for those. → {} ⚠ broad scope — affects all matching teams simultaneously; not atomic (partial failure possible); may disrupt active distributions; workspace memberships unaffected (use workspace-remove-users-all separately) see: user-find (resolve userIds), workspace-list (find workspaceIds), team-list (verify after), workspace-remove-users-all (for workspace offboarding)

tenant-get

ChatGPT
Fetches top-level config and metadata for the authenticated org — the tenant is inferred from the API key, so there are no inputs. Use it to learn the org's subdomain and cluster, which other calls fold into URLs and identifiers. → {tenantData: {tenantId, cluster, subdomain}, tenantType: "Personal"|"Organization", email (Personal only), domains: [...] (Organization only)} Note: tenantId is at tenantData.tenantId (not top-level); subdomain is at tenantData.subdomain see: meeting-activity/crm-activity (use tenantData.subdomain for URL construction)

user-find

ChatGPT
Resolves an email or name to the userId that team, workspace, distribution, and license calls all reference. Matches the free-text query against email and name (partial matches). Paginated (default pageSize 200). → paginated list of users, each with: {id, name, email, isSuperAdmin, licenses, workspaces, salesforce (opt), hubspot (opt), slug (opt), managedWorkspaces, managedTeams} see: user-read (full profile by id), user-find-by-ids (batch by id), user-find-by-filter (structured multi-field filtering)

user-find-by-filter

ChatGPT
Searches users with a structured UserFilterRequest — combine free-text search, an exact userIds list, isAdmin, a single workspace, and ordering when user-find's plain query is too coarse. Paginated (default pageSize 200). Note: field names are exact and unforgiving — searchEmails (not "emails"), workspace (not "workspaceId"), excludedUsers (not "excludeUserIds"), and ordering as a nested object {column: "Name"|"Email", direction: "Asc"|"Desc"} (not "orderBy"/"orderDirection"). A wrong name is silently ignored and returns unfiltered results with no error. → paginated list of users, each with: {id, name, email, isSuperAdmin, licenses, workspaces, salesforce (opt), hubspot (opt), slug (opt), managedWorkspaces, managedTeams} see: workspace-list (find workspaceId), user-find (simpler name/email search), user-update-licenses/team-add-users/workspace-add-users (act on results)

user-find-by-ids

ChatGPT
Batch-fetches full profiles for a known set of userIds in one request — the id-list counterpart to user-read. The body is a bare JSON array of userId UUIDs (e.g. ["uuid1", "uuid2"]), not wrapped in an object. → paginated list of users, each with: {id, name, email, isSuperAdmin, licenses, workspaces, salesforce: {email, id} (opt), hubspot: {email, id} (opt), slug (opt), managedWorkspaces, managedTeams} see: user-find/find-by-filter (look up userIds), user-update-licenses/team-add-users/workspace-add-users (act on results)

user-invite

ChatGPT
Onboards a new person: creates their CP account. By default (sendInvite omitted or false) the account is created in the not-yet-invited state and no email is sent — send it later in bulk with user-send-invites. Set sendInvite=true to also email the invitation immediately. The email must not already belong to a user in the org. Optionally set roles (User|Admin|WorkspaceManager|TeamManager, default User), initial licenses (an array of license-type strings, e.g. ["Distro","ChiliCalOrg"] — valid values: Distro, ChiliCalOrg, Concierge, ConciergeLive, Chat, Handoff), a salesforceId link (see warning below), and workspaces to join at invite time. → {userId} ⚠ non-idempotent — a duplicate email fails with 409 edge.user.already-exists; with sendInvite=true the invitation email is sent immediately and cannot be unsent; fails if org lacks seats ⚠ salesforceId is currently NOT persisted (the invite-time Salesforce mapping was removed; CRM mapping is now handled by Salesforce sync) — do not rely on it sticking; verify via user-read if you need the mapping. see: user-send-invites (email the invitation later), user-find (check email doesn't exist first), user-read (verify after), workspace-list (resolve workspaces), team-add-users (add to team)

user-read

ChatGPT
Fetches the full profile for one user once you already hold their userId. To go the other way — email or name to userId — use user-find first. Unlike the list endpoints, this returns the full profile including personal details, working hours, and timezone. → {id, name, email, isSuperAdmin, licenses, workspaces (set of workspaceIds), salesforce: {email, id} (opt), hubspot: {email, id} (opt), slug (opt), managedWorkspaces, managedTeams, personalDetails (opt): {firstName, lastName, jobTitle, conferenceDetails, location, phoneNumber, imageUrl, customCompanyLogoUrl}, workingHours (opt): {monday..sunday: [{start, end}]}, timezone (opt)} Note: top-level field is id (not userId); workspaces (not workspaceMemberships); salesforce is an object {email, id} (not a string salesforceId); personalDetails/workingHours/timezone are populated here but null on the list endpoints see: user-find (look up userId by email/name), user-find-by-ids (batch fetch), user-update (edit profile), user-update-licenses/workspace-add-users/team-add-users (act on user)

user-send-invites

ChatGPT
Sends invitation emails to existing users who have not yet been invited, or whose last invite is past the re-invite cooldown. - userIds (opt): list of user IDs to notify; omit to send to all eligible users in the org → {} ⚠ non-idempotent — triggers emails; users within the cooldown window are silently skipped see: user-invite (create a new user account, optionally sending the invite in one step), user-find (look up user IDs)

user-update

ChatGPT
Updates any subset of a user's profile in a single call. Each scalar field is tri-state: OMIT it to leave it unchanged, send null to CLEAR it, or send a value to SET it. Requires the userId (use user-find first). For managing licenses, roles, or workspace membership use user-update-licenses / workspace-add-users / team-add-users instead. - firstName / lastName (opt, nullable): the user's name; either can be set independently - jobTitle (opt, nullable): the user's title - conferenceDetails (opt, nullable): free-text conferencing / video-meeting details shown on their meetings - location (opt, nullable): the user's location - phoneNumber (opt, nullable): the user's phone number - slug (opt, nullable): the user's personal booking-page slug; must be unique in the tenant — a clash returns 409. null removes the slug (deletes the booking link); changing it retires the old slug (existing links stop working) - timezone (opt, nullable): IANA timezone id (e.g. "America/New_York") to pin the scheduling timezone; null reverts to syncing from the connected calendar - workingHours (opt): full weekly working hours ({monday..sunday: [{start:"09:00", end:"17:00"}]}); replaces the user's current hours. Empty day list = unavailable. Not nullable — omit to leave unchanged → updated user profile (same shape as user-read) ⚠ working hours is a full replacement; scalar fields are a tri-state patch (omit=keep, null=clear, value=set) see: user-read (verify), user-find (resolve userId), user-update-licenses (licenses)

user-update-licenses

ChatGPT
Bulk-updates CP product licenses for one or more users. Unlisted users are unaffected. - update (req): {userId: {distro, chiliCalOrg, concierge, conciergeLive, chat, handoff (booleans, omitted = false); tier (opt): RoutingAndScheduling|Experiences|ChiliDataPlatform}}; use user-find for userIds → {} ⚠ downgrade takes effect immediately (disables gated features) ⚠ each listed user's licenses are fully REPLACED, not patched: every omitted flag becomes false and an omitted tier is cleared. If this leaves the user with no license at all, their workspace membership is also cleared. Read current licenses first (user-read) and send back every license you want to keep. ⚠ a grant (product flag or tier) only takes effect if the org has an available seat for it; with no seat that license silently keeps its previous value and no error is returned. Always read back with user-read/user-find to confirm what actually applied. see: user-find/find-by-ids (resolve userIds and verify after)

web-experience-create

ChatGPT
Creates a Web Experience in a workspace from full, typed playbook content and immediately publishes it, so it goes Live (Enabled) and visible to site visitors at once — there is no draft-only create via this API. Request body: {name, widgetType: "Chat"|"Scheduling"|"Offer"|"Message", trigger, conversation, passThrough?, languageSettings?} Note: conversation node rules (the server rejects a playbook that breaks them): - StartLiveChat needs, on every path that reaches it: (1) an earlier email-collecting node, i.e. a FillForm with the PersonEmail data field, or a ScheduleMeeting; else it fails with "Node with id: 'N' does not have an email input before". (2) An earlier visitor action: ReplyButtons, FillForm or PiperAiStrategyNode. (3) Per assignee: Owner needs an earlier RoutingRule or StartLiveChat; Meeting needs an earlier ScheduleMeeting; AssignmentTableColumn needs an earlier RoutingRule with the same ruleId; EveryoneInTeam needs a WaitForRepToJoin after it (on onAssignmentExit). Its onAssignmentExit can pass only through side-effect nodes (CrmAction, notifications, Message) before a WaitForRepToJoin. - NavigateTo is an in-journey jump: its target is another node id in the same conversation and it has no visible effect. It is not a URL redirect; to send the visitor to a URL, use RedirectTo. → {id, workspaceId, name, widgetType, trigger, conversation, passThrough?, languageSettings?, draftCreator, state: "Enabled", version, publisher, enabledCrms?} Note: the response is the created, published playbook view; state is "Enabled" and the published fields (version, publisher) are populated. ⚠ the experience goes Live on the customer's website immediately ⚠ returns 403 edge.web-experience-management-disabled when Web Experience management via API is not enabled for the tenant; nothing is changed ⚠ if the create fails, cleanup of the new draft is best-effort: a draft can remain (e.g. when the backend rejects the create on a business rule) and it can be missing from web-experience-list. Before you retry, look in web-experience-list for an entry with the same name and no state, and delete it with web-experience-delete. If it is not listed, tell the user to delete it in the Chili Piper app see: web-experience-list (find a workspaceId), web-experience-update (rename or pause/resume)

web-experience-delete

ChatGPT
Deletes a Web Experience by id — the draft and every published version. Returns 204 No Content on success. → {} ⚠ irreversible via API; if the experience is Live it disappears from the customer's website ⚠ returns 403 edge.web-experience-management-disabled when Web Experience management via API is not enabled for the tenant; nothing is changed see: web-experience-get (confirm the experience before deleting), web-experience-update (pause instead of deleting)

web-experience-get

ChatGPT
Fetches one Web Experience by id, as a full playbook view (current draft content + latest published state). → {id, workspaceId, name, widgetType: "Chat"|"Scheduling"|"Offer"|"Message", trigger, conversation, passThrough?, languageSettings?, draftCreator, state?: "Enabled"|"Disabled"|"Removed", version?, publisher?, enabledCrms?} Note: an absent state means the experience is still a draft (never published). see: web-experience-list (find ids)

web-experience-list

ChatGPT
Lists the tenant's Web Experiences (on-site Chat, Scheduling, Offer and Announcement embeds), each as a full playbook view: its current draft content plus its latest published state. Scope to one workspace with workspaceId, or omit it to fan out across all of the tenant's workspaces. → [{id, workspaceId, name, widgetType: "Chat"|"Scheduling"|"Offer"|"Message", trigger, conversation, passThrough?, languageSettings?, draftCreator, state?: "Enabled"|"Disabled"|"Removed", version?, publisher?, enabledCrms?}] Note: widgetType "Message" is the Announcement experience. trigger and conversation are the current draft content. The published-only fields (state, version, publisher, enabledCrms) appear only once the experience has been published; an absent state means it is still a draft — never published and not visible to site visitors. see: web-experience-get (one by id), web-experience-create (add one), web-experience-update (rename or enable/disable), web-experience-delete (remove one)

web-experience-logs

ChatGPT
Reads a workspace's Web Experience session logs over a time window — the read-only audit trail across every flow type (Chat, Scheduling, Offer, Announcement) of who engaged, how they were routed, and what got booked. Each row carries the routing outcome, the assignee roster, any meetings booked, plus the resolved CRM id, captured data fields and UTM parameters. The start/end window (ISO-8601) spans at most 30 days. Pass flowType (repeatable) to restrict to specific flows; omit it for all flow types. Pass playbookId (repeatable) to restrict to specific experiences. Narrow to a specific guest with guestEmail (case-insensitive exact match) and/or guestId (exact match). Pages start at 0; pageSize defaults to 10, max 50. → {results: [{guestId, sessionId, playbookId, flowType: "Chat"|"Scheduling"|"Offer"|"Announcement", guestEmail, startedAt, lastGuestInteractionAt, ended, targetedUrl, respondedUrl, routingOutcome, ruleId, ruleName, evaluatedRules: [{ruleId, ruleName, ruleType, matched, evaluatedAt}], assignments: [{assigneeId, method, assignmentType}], conversationAssigneeId, routedAt, meetingBooked, meetings: [{assigneeId, origin, scheduledAt}], crmIds: [{type, objectType, value}], dataFields, utms, chatAiStarted, repJoined, messages: [{role, content, timestamp}]}], total, page, pageSize} Note: routingOutcome is one of Routed | NotRouted | Abandoned; role is one of Bot | Guest; meetings[N].origin is one of Journey | Inbox | NoMeeting. flowType-specific fields that cannot apply to a row are absent, not empty: chatAiStarted, repJoined and messages appear only on Chat rows. see: web-experience-list (find playbookId), workspace-list (find workspaceId)

web-experience-update

ChatGPT
Patches a Web Experience — edit its content, rename it, and/or pause/resume it — then republishes so changes are Live immediately. Every field is optional; send only what you want to change and omitted fields keep their current value. enabled=true sets it Live (Enabled), enabled=false sets it Paused (Disabled). trigger, conversation, passThrough, and languageSettings patch the draft content (omitted content fields keep their current value). conversation REPLACES the whole conversation graph: send every node (get the current one with web-experience-get and edit it), never only the changed nodes. passThrough resets to {} when conversation is sent without it. Invalid content (e.g. an exit to a missing node) is rejected with 400 and the experience stays unchanged. Note: a new conversation must follow the same node rules as web-experience-create (StartLiveChat needs an earlier email-collecting node, an earlier visitor action and its assignee's prerequisites; NavigateTo jumps to another node, it is not a URL redirect). → {id, workspaceId, name, widgetType, trigger, conversation, passThrough?, languageSettings?, draftCreator, state, version, publisher, enabledCrms?} Note: the response is the updated, published playbook view. Renaming can fail with 423 while someone is editing the draft in the app's builder; on failure before the republish, the draft content and name are restored. ⚠ changes take effect immediately on the customer's website ⚠ returns 403 edge.web-experience-management-disabled when Web Experience management via API is not enabled for the tenant; nothing is changed see: web-experience-get (check the current state before patching)

webhook-create

ChatGPT
Subscribes a url to a meeting lifecycle event (MeetingCreated | MeetingUpdated | MeetingDeleted); each firing delivers a signed POST to that absolute https url. The (triggerType, url) pair is the webhook's identity, so creating a duplicate is rejected. Set enabled=false to create it paused (no deliveries until a later update enables it). → the created {triggerType, url, enabled} see: webhook-list (see existing webhooks), webhook-update, webhook-delete

webhook-delete

ChatGPT
Unsubscribes a webhook, matched by its (triggerType, url) identity; deliveries for that subscription stop immediately. → {} ⚠ irreversible — re-create with webhook-create to restore see: webhook-list (verify before and after)

webhook-get-secret

ChatGPT
Returns the tenant's webhook signing secret — the single key shared by all of the tenant's webhooks that signs every delivery. Verify a delivery by computing HMAC-SHA256 over the string "{X-Chili-Timestamp header}.{raw request body}" with this secret, then comparing the lowercase hex digest to the X-Chili-Signature header. The secret is provisioned automatically when the first webhook is created; if none exists yet this returns 404 — create a webhook, or call webhook-rotate-secret to provision one. → {secret} see: webhook-rotate-secret (generate a new secret), webhook-create

webhook-list

ChatGPT
Returns all webhooks configured for the tenant. A webhook delivers a signed POST to its url whenever the meeting lifecycle event fires. → {webhooks: [{triggerType, url, enabled}]} see: webhook-create (add one), webhook-update (toggle/edit), webhook-delete (remove one)

webhook-rotate-secret

ChatGPT
Generates a new webhook signing secret for the tenant and returns it. The previous secret is invalidated immediately, so switch your signature verification to the new value right away. Also use this to provision a secret before any webhook exists. → {secret} ⚠ irreversible — the previous secret stops verifying deliveries immediately see: webhook-get-secret (read the current secret)

webhook-update

ChatGPT
Toggles a webhook's enabled state (pause or resume deliveries), matched by its (triggerType, url) identity. That pair is immutable here — to change the url or event, delete and re-create. Fails if no matching webhook exists. → the updated {triggerType, url, enabled} see: webhook-list (find the webhook), webhook-create, webhook-delete

workspace-add-users

ChatGPT
Grants users access to a workspace so they can use its teams, links, and distributions. Already-members are silently skipped. → {} see: workspace-list (find workspaceId), user-find (resolve emails to userIds), workspace-list-users (verify after)

workspace-create

ChatGPT
Creates a new, empty team workspace. Names need not be unique — check workspace-list first to avoid duplicates. The new workspace has no members, teams or managers. → {id, name, emoji, logo, metadata} Note: id is the new workspaceId. see: workspace-add-users (add members), team-create (add teams), workspace-list (check for an existing workspace first)

workspace-delete

ChatGPT
Deletes (soft-deletes) a team workspace. It is never blocked: all members lose access to it and its Workspace Managers lose that role. Delete or reassign its teams, meeting types and routers first if they are still needed. → {id, name, emoji, logo, metadata} Note: returns the deleted workspace. ⚠ destructive — members and Workspace Managers are removed from the workspace; confirm with the user first see: workspace-list-users (check who is affected first), workspace-list (find workspaceId)

workspace-get

ChatGPT
Reads one team workspace by its workspaceId. Personal workspaces are rejected. → {id, name, emoji, logo, metadata} see: workspace-list (find workspaceId), workspace-update, workspace-list-users (members)

workspace-list

ChatGPT
Lists the workspaces that partition the org and yields the workspaceId most other calls need to scope their work. Hierarchy: Tenant → Workspaces → Teams → Users. Paginated: HTTP default pageSize 10; MCP returns up to 200 when pagination is omitted. Optionally filter by name (case-insensitive substring) and workspaceIds. Filters apply before pagination, so pages contain only matching workspaces. → [{id, name, emoji, logo, metadata}] Note: id is the workspaceId. HTTP wraps the items as {results, total, page, pageSize}; MCP returns {items} and adds nrOfUsers. see: workspace-get (one workspace by id), workspace-create, workspace-list-users (members), workspace-add/remove-users (manage membership), meeting-list/export (filter by workspaceId), concierge-logs (filter by workspaceId)

workspace-list-users

ChatGPT
Lists the members of a workspace; omit workspaceId to list every user in the org. Paginated: HTTP default pageSize 10; MCP returns up to 200 when pagination is omitted. → [{id, name, email, isSuperAdmin, managedWorkspaces, managedTeams, licenses, workspaces, personalWorkspaceId, ...}] Note: HTTP wraps the users as {results, total, page, pageSize}; MCP returns {items}. see: workspace-list (resolve workspaceId), workspace-add/remove-users (manage membership), user-find-by-filter (structured multi-field filtering)

workspace-remove-users

ChatGPT
Revokes users' access to a single workspace; their other workspace and their team memberships are untouched. To clear a user from every workspace at once, use workspace-remove-users-all instead. → {} ⚠ may affect active distributions and routing rules within the workspace see: workspace-list (find workspaceId), workspace-list-users (verify after), workspace-remove-users-all (all workspaces at once), team-remove-users (for team offboarding)

workspace-remove-users-all

ChatGPT
Strips users out of every workspace at once, the workspace half of offboarding. Accounts stay active, team memberships stay intact, and licenses stay assigned — pair with team-remove-users-all and user-update-licenses to fully offboard. → {} ⚠ broad scope — use primarily for offboarding; not atomic (partial failure possible); may disrupt active distributions; use team-remove-users-all and user-update-licenses separately see: user-find (resolve userIds), team-remove-users-all (team offboarding), user-update-licenses (revoke licenses)

workspace-update

ChatGPT
Partially updates a team workspace: only the fields you send change. Send null for emoji or logo to clear it; name cannot be cleared. → {id, name, emoji, logo, metadata} see: workspace-get (current values), workspace-list (find workspaceId)

Capabilities

Writes

Example Prompts

Click any prompt to copy it.

Similar Apps