From f4427e44c1ffc0fcef3db2a3d98cef0a827fb55d Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Tue, 17 Feb 2026 11:41:58 +0530 Subject: [PATCH 01/44] link override v2 --- modules/ROOT/pages/customize-links.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ROOT/pages/customize-links.adoc b/modules/ROOT/pages/customize-links.adoc index 384a48ac9..1ab243677 100644 --- a/modules/ROOT/pages/customize-links.adoc +++ b/modules/ROOT/pages/customize-links.adoc @@ -232,6 +232,10 @@ appEmbed.render(); + . Click *Save changes*. +== Override ThoughtSpot URLs +The link override settings allows embedded users to override native ThoughtSpot URLs so the navigation stays within their host application. +You must set the `enableLinkOverridesV2` to `true` in the Visual Embed SDK to override the links of your embedded application pages and navigation links. +Once set to `true`, all links will show the host application URLs when hovered over or clicked. == Verify system-generated links From e0e939062d0ab19ab8ed36d3f0b8e9b131b44356 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Thu, 19 Feb 2026 10:24:16 +0530 Subject: [PATCH 02/44] deprecation for sage privilege --- modules/ROOT/pages/deprecated-features.adoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/ROOT/pages/deprecated-features.adoc b/modules/ROOT/pages/deprecated-features.adoc index b64e3d751..23afffe63 100644 --- a/modules/ROOT/pages/deprecated-features.adoc +++ b/modules/ROOT/pages/deprecated-features.adoc @@ -14,6 +14,7 @@ As ThoughtSpot applications evolve, some existing features will be deprecated an [options='header'] |===== |Feature|Impacted interface and release versions|Deprecation date |End of Support / removal from the product +a|xref:deprecated-features.adoc#SagePrivilegeDeprecation[`PREVIEW_THOUGHTSPOT_SAGE` privilege deprecation] a|ThoughtSpot Cloud 26.3.0.cl and later | March 2026 | May 2026 a|xref:deprecated-features.adoc#_answer_data_panel_classic_experience_deprecation[Answer Data panel classic experience] |ThoughtSpot Cloud 26.4.0.cl and later | April 2026 | August 2026 a|xref:deprecated-features.adoc#_worksheet_deprecation_and_removal[Worksheets] a| ThoughtSpot Cloud 10.4.0.cl and later |November 2024 | September 2025 @@ -80,6 +81,18 @@ a|xref:deprecated-features.adoc#_deprecated_parameter_in_rest_api_v2_0_authentic |||| |===== +[#SagePrivilegeDeprecation] +== `PREVIEW_THOUGHTSPOT_SAGE` privilege deprecation +The `PREVIEW_THOUGHTSPOT_SAGE` privilege is renamed to `CAN_USE_SPOTTER` with the ThoughtSpot 26.3.0.cl release version. + +Impact on your instance:: +* Both privileges remain supported until the ThoughtSpot 26.5.0.cl release, at which point `PREVIEW_THOUGHTSPOT_SAGE` will be removed. +* For existing ThoughtSpot instances which have enabled RBAC before the 26.3.0.cl release, there will be no automatic changes to the roles created using the `PREVIEW_THOUGHTSPOT_SAGE` privilege. However, the underlying privilege associated with such roles will be renamed to `CAN_USE_SPOTTER`. + +Recommended action:: +For ThoughtSpot instances which have enabled RBAC before the 26.3.0.cl release, the admins will have to create a role in accordance with the newer privilege name. + + == Answer Data panel classic experience deprecation The classic Data panel experience in Search and Answer pages will be deprecated in ThoughtSpot 26.4.0.cl release version. The new data panel experience, which provides a more intuitive layout with improved organization of data elements and features such as query sets and custom groups, will be the default data panel experience on all ThoughtSpot Embedded instances using Visual Embed SDK v1.41.1 or later. From b371d60442902c1b89decebfbec867237a72bb20 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Mon, 23 Feb 2026 11:43:52 +0530 Subject: [PATCH 03/44] deprecation edits --- modules/ROOT/pages/deprecated-features.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/deprecated-features.adoc b/modules/ROOT/pages/deprecated-features.adoc index 23afffe63..68581fbfb 100644 --- a/modules/ROOT/pages/deprecated-features.adoc +++ b/modules/ROOT/pages/deprecated-features.adoc @@ -14,7 +14,7 @@ As ThoughtSpot applications evolve, some existing features will be deprecated an [options='header'] |===== |Feature|Impacted interface and release versions|Deprecation date |End of Support / removal from the product -a|xref:deprecated-features.adoc#SagePrivilegeDeprecation[`PREVIEW_THOUGHTSPOT_SAGE` privilege deprecation] a|ThoughtSpot Cloud 26.3.0.cl and later | March 2026 | May 2026 +a|xref:deprecated-features.adoc#SagePrivilegeDeprecation[`PREVIEW_THOUGHTSPOT_SAGE` privilege deprecation] a|ThoughtSpot Cloud 26.3.0.cl and later | March 2026 | September 2026 a|xref:deprecated-features.adoc#_answer_data_panel_classic_experience_deprecation[Answer Data panel classic experience] |ThoughtSpot Cloud 26.4.0.cl and later | April 2026 | August 2026 a|xref:deprecated-features.adoc#_worksheet_deprecation_and_removal[Worksheets] a| ThoughtSpot Cloud 10.4.0.cl and later |November 2024 | September 2025 @@ -86,7 +86,7 @@ a|xref:deprecated-features.adoc#_deprecated_parameter_in_rest_api_v2_0_authentic The `PREVIEW_THOUGHTSPOT_SAGE` privilege is renamed to `CAN_USE_SPOTTER` with the ThoughtSpot 26.3.0.cl release version. Impact on your instance:: -* Both privileges remain supported until the ThoughtSpot 26.5.0.cl release, at which point `PREVIEW_THOUGHTSPOT_SAGE` will be removed. +* Both privileges remain supported until the ThoughtSpot 26.9.0.cl release, at which point `PREVIEW_THOUGHTSPOT_SAGE` will be removed. * For existing ThoughtSpot instances which have enabled RBAC before the 26.3.0.cl release, there will be no automatic changes to the roles created using the `PREVIEW_THOUGHTSPOT_SAGE` privilege. However, the underlying privilege associated with such roles will be renamed to `CAN_USE_SPOTTER`. Recommended action:: From 382f99fc3c8a2da10429dc7603e733faefe4444b Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Mon, 23 Feb 2026 11:46:17 +0530 Subject: [PATCH 04/44] changed docconfig to 26.3 --- src/configs/doc-configs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configs/doc-configs.js b/src/configs/doc-configs.js index a958912d0..637c2ec28 100644 --- a/src/configs/doc-configs.js +++ b/src/configs/doc-configs.js @@ -48,10 +48,10 @@ module.exports = { }, VERSION_DROPDOWN: [ { - label: '26.2.0.cl', + label: '26.3.0.cl', link: ' ', subLabel: 'Cloud (Latest)', - iframeUrl: 'https://developer-docs-26-2-0-cl.vercel.app/docs/', + iframeUrl: 'https://developer-docs-26-3-0-cl.vercel.app/docs/', }, ], CUSTOM_PAGE_ID: { From 79329edad948e42376c995e134bb9e77d72c4383 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Tue, 24 Feb 2026 11:16:44 +0530 Subject: [PATCH 05/44] changed the lb styling flag --- modules/ROOT/pages/customize-css-styles.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/customize-css-styles.adoc b/modules/ROOT/pages/customize-css-styles.adoc index f2f06e780..f16d7232d 100644 --- a/modules/ROOT/pages/customize-css-styles.adoc +++ b/modules/ROOT/pages/customize-css-styles.adoc @@ -189,7 +189,7 @@ Use the following variables to customize the Liveboard visualization groups and [NOTE] ==== -To enable this feature contact ThoughtSpot support and set `isLiveboardStylingAndGroupingEnabled` to `true` in the SDK . +To enable this feature contact ThoughtSpot support and set `isLiveboardMasterpiecesEnabled` to `true` in the SDK . ==== [width="100%" cols="7,7"] From 598470db1faad4a5809e7490dff05017583246a1 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Tue, 24 Feb 2026 13:05:24 +0530 Subject: [PATCH 06/44] changes for Spotter privilege --- modules/ROOT/pages/roles.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/roles.adoc b/modules/ROOT/pages/roles.adoc index 68a8727f7..a4e0afd38 100644 --- a/modules/ROOT/pages/roles.adoc +++ b/modules/ROOT/pages/roles.adoc @@ -126,7 +126,7 @@ UI: *Has developer privilege* a| Allows users to access the following features UI: *Can schedule for others* |Allows users to schedule, edit, and delete Liveboard jobs. |ThoughtSpot Sync|API: `SYNCMANAGEMENT` + UI: *Can manage sync settings* | Allows setting up secure pipelines to external business apps and sync data using ThoughtSpot Sync. -|ThoughtSpot Spotter|API: `PREVIEW_THOUGHTSPOT_SAGE` + +|ThoughtSpot Spotter|API: `CAN_USE_SPOTTER` + UI: *Can use Spotter* | Allows access to ThoughtSpot Spotter and natural language query and answer generation. |Catalog management|API: `CAN_CREATE_CATALOG` + UI: *Can manage catalog*| Allows users to create, edit, and manage a link:https://docs.thoughtspot.com/cloud/latest/catalog-integration[data connection to Alation, window=_blank], and import metadata. From 8ab458b1ed5fb7be1f31d092d0e3bea23d17b4b1 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Tue, 24 Feb 2026 07:21:32 +0530 Subject: [PATCH 07/44] SCAL-215583 --- modules/ROOT/pages/whats-new.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index c59a7b19b..9a0f677fd 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -78,6 +78,9 @@ For more information, see xref:spotter-apis.adoc[Spotter APIs]. You can now intercept API calls from the embedded ThoughtSpot application using the `interceptUrls` attribute in the Visual Embed SDK. This feature lets you control API requests in your embedding application and use embed events to modify, block, or handle requests before they are sent to the backend. For more information, see xref:api-intercept.adoc[Intercept API calls and search requests]. +=== Icon customization enhancements +You can now replace or customize the chart switcher toggle and icons in the Charts drawer on an Answer or visualization page using SVG sprites. Previously, these icons were fixed to ThoughtSpot defaults and were not configurable. In the new version, these icons are available as SVG components and can be replaced by developers through the xref:customize-icons.adoc[icon customization framework] as needed. + === Mobile Embed SDK The SDKs for embedding ThoughtSpot components in mobile apps are now Generally Available (GA). For more information about the SDKs and how to embed a ThoughtSpot component in a mobile app, see xref:mobile-embed.adoc[Mobile embed documentation]. From c3d9491605fb1072e3d5b0f558bad3b1693935fd Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Wed, 25 Feb 2026 09:59:42 +0530 Subject: [PATCH 08/44] link override updates --- modules/ROOT/pages/api-changelog.adoc | 11 +++++++++++ modules/ROOT/pages/customize-links.adoc | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index 9792b332c..aebb0b3ee 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -8,6 +8,17 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For information about new features and enhancements available for embedded analytics, see xref:whats-new.adoc[What's New]. +== March 2026 +[width="100%" cols="1,4"] +|==== + +|[tag redBackground]#DEPRECATED# | ** Use `enableLinkOverridesV2` instead of `linkOverride` ** + + +The `linkOverride` parameter is deprecated in Visual Embed SDK v------- and later. +To override native ThoughtSpot URLs with the host application URLs, use the `enableLinkOverridesV2` attribute instead. + + + == Version 1.45.0, February 2026 [width="100%" cols="1,4"] diff --git a/modules/ROOT/pages/customize-links.adoc b/modules/ROOT/pages/customize-links.adoc index 1ab243677..24439aba8 100644 --- a/modules/ROOT/pages/customize-links.adoc +++ b/modules/ROOT/pages/customize-links.adoc @@ -233,9 +233,22 @@ appEmbed.render(); . Click *Save changes*. == Override ThoughtSpot URLs -The link override settings allows embedded users to override native ThoughtSpot URLs so the navigation stays within their host application. -You must set the `enableLinkOverridesV2` to `true` in the Visual Embed SDK to override the links of your embedded application pages and navigation links. -Once set to `true`, all links will show the host application URLs when hovered over or clicked. +Link override settings allow embedded users to redirect native ThoughtSpot URLs to links within their host application. To enable this, set `enableLinkOverridesV2` to `true` in the Visual Embed SDK. Once enabled, all links will display host application URLs when hovered over or opened in a new tab. +These settings apply work the same for multi-tenant ThoughtSpot embedded instances too. + +[source,JavaScript] +---- +const appEmbed = new AppEmbed(document.getElementById('ts-embed'), { + frameParams: { + width: '100%', + height: '100%', + }, + pageId: Page.Home, + showPrimaryNavbar: true, + enableLinkOverridesV2: true, +}); +appEmbed.render(); +---- == Verify system-generated links From 48d3c35cad9c6ef3bcef1c3a15205009ad48b71e Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Wed, 25 Feb 2026 12:55:07 +0530 Subject: [PATCH 09/44] changes for override link settings --- modules/ROOT/pages/api-changelog.adoc | 7 +++---- modules/ROOT/pages/customize-links.adoc | 18 +++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index aebb0b3ee..cd0e0e15e 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -12,12 +12,11 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For in [width="100%" cols="1,4"] |==== -|[tag redBackground]#DEPRECATED# | ** Use `enableLinkOverridesV2` instead of `linkOverride` ** + - -The `linkOverride` parameter is deprecated in Visual Embed SDK v------- and later. -To override native ThoughtSpot URLs with the host application URLs, use the `enableLinkOverridesV2` attribute instead. +|[tag greenBackground]#NEW FEATURE# | **`enableLinkOverridesV2`** + +Use this enhanced configuration to override ThoughtSpot URLs on hover or when opening in a new tab. This is recommended over the earlier `linkOverride` flag for a better user experience. +|==== == Version 1.45.0, February 2026 diff --git a/modules/ROOT/pages/customize-links.adoc b/modules/ROOT/pages/customize-links.adoc index 24439aba8..a94e625c5 100644 --- a/modules/ROOT/pages/customize-links.adoc +++ b/modules/ROOT/pages/customize-links.adoc @@ -211,10 +211,15 @@ The default link format is `\https://{ThoughtSpot-Host}/#/\{path}`. If your host https://www.mysite.com/{path} ---- -+ -You must also set the `linkOverride` to `true` in the Visual Embed SDK to override the link format of your embedded application pages and navigation links: +. Click *Save changes*. + +== Override ThoughtSpot URLs + +Link override settings allow embedded users to redirect native ThoughtSpot URLs to links within their host application. ThoughtSpot supports two Visual Embed SDK configurations for overriding links generated by ThoughtSpot. These settings work the same for multi-tenant ThoughtSpot embedded instances too. + +You can set the `linkOverride` to `true` in the Visual Embed SDK to override the link format of your embedded application pages and navigation links. Once enabled, all links will display host application URLs when opened in a new tab. + -+ [source,JavaScript] ---- const appEmbed = new AppEmbed(document.getElementById('ts-embed'), { @@ -229,12 +234,7 @@ const appEmbed = new AppEmbed(document.getElementById('ts-embed'), { appEmbed.render(); ---- -+ -. Click *Save changes*. - -== Override ThoughtSpot URLs -Link override settings allow embedded users to redirect native ThoughtSpot URLs to links within their host application. To enable this, set `enableLinkOverridesV2` to `true` in the Visual Embed SDK. Once enabled, all links will display host application URLs when hovered over or opened in a new tab. -These settings apply work the same for multi-tenant ThoughtSpot embedded instances too. +Set `enableLinkOverridesV2` to `true` in the Visual Embed SDK. Once enabled, all links will display host application URLs when hovered over or opened in a new tab. ThoughtSpot recommends using this enhanced configuration for you link override settings. [source,JavaScript] ---- From c7b441c6f03a5be62eaec66fd3beaac277e5f51c Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Tue, 24 Feb 2026 11:16:44 +0530 Subject: [PATCH 10/44] 26.3 updates --- modules/ROOT/pages/api-changelog.adoc | 45 ++ modules/ROOT/pages/common/nav.adoc | 3 +- modules/ROOT/pages/embed-spotter.adoc | 35 +- .../pages/events-context-aware-routing.adoc | 396 ++++++++++++++++++ modules/ROOT/pages/rest-apiv2-changelog.adoc | 37 ++ modules/ROOT/pages/spotter-apis.adoc | 100 ++++- modules/ROOT/pages/whats-new.adoc | 34 ++ 7 files changed, 641 insertions(+), 9 deletions(-) create mode 100644 modules/ROOT/pages/events-context-aware-routing.adoc diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index 5840406b4..9699da074 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -8,6 +8,51 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For information about new features and enhancements available for embedded analytics, see xref:whats-new.adoc[What's New]. +== Version 1.46.x, March 2026 + +[width="100%" cols="1,4"] +|==== +|[tag greenBackground]#NEW FEATURE# a| **Spotter experience** +The SDK includes the following parameters, action IDs, and events to customizing Spotter embed experience. + +Chat history sidebar customization:: + +* `SpotterSidebarViewConfig` interface with configuration parameters for customizing the visibility and appearance of the chat history sidebar. +* `spotterSidebarConfig` properties for customizing the appearance and available options in the chat history sidebar. +* Action IDs for customizing the visibility and status of the actions in the embedded Spotter interface: +** `Action.DataModelInstructions` for the data model instructions icon. +** `Action.SpotterSidebarHeader` for the chat history sidebar header +** `Action.SpotterSidebarFooter` for the chat history sidebar header +** `Action.SpotterSidebarToggle` for the chat history toggle that expands or collapses the sidebar. +** `Action.SpotterNewChat` for the new chat icon in the chat history sidebar +** `Action.SpotterPastChatBanner` for banner in the chat history sidebar +** `Action.SpotterChatMenu` for the chat menu component in the chat history sidebar +** `Action.SpotterChatRename` for **Rename** action in the chat menu of a saved chat. +** `Action.SpotterChatDelete` for **Delete** action in the chat menu of a saved chat. +//** `Action.SpotterDocs` for best practices documentation icon in the chat history sidebar. + +Events:: +* `HostEvent.DataModelInstructions` + +Opens the Data Model instructions modal. +* `EmbedEvent.DataModelInstructions` + +Is emitted when a user clicks the Data model instructions icon in the Spotter interface. +* `EmbedEvent.SpotterConversationRenamed` + +Is emitted when a user renames a saved chat. +* `EmbedEvent.SpotterConversationDeleted` + +Is emitted when a saved chat is deleted. +* `EmbedEvent.SpotterConversationSelected` + +Is emitted when a saved chat is selected in the chat history sidebar. + +|[tag greenBackground]#NEW FEATURE# a| **Page context settings for host events** +The Visual Embed SDK includes the `getCurrentContext()` function to fetch the current context and route host events to a specific xref:ContextType.adoc[context type] in the embedded view. +For more information, see xref:events-context-aware-routing.adoc[Host events in multi-modal contexts]. + +|[tag redBackground]#DEPRECATED# a| **dataPanelV2** + +The `dataPanelV2` parameter is deprecated and can no longer be used to switch between the classic and new data panel experience. By default, the new data panel v2 experience is enabled on all ThoughtSpot embedded instances. +|==== + + == Version 1.45.0, February 2026 [width="100%" cols="1,4"] diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index e8301160f..7fe2123f0 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -99,7 +99,8 @@ *** link:{{navprefix}}/action-config[Customize menus] **** link:{{navprefix}}/actions[Action IDs in the SDK] *** link:{{navprefix}}/events-app-integration[Events and app interactions] -**** link:{{navprefix}}/api-search-intercept[Intercept API and data fetch requests] +**** link:{{navprefix}}/context-aware-event-routing[HostEvents in multi‑modal contexts] +**** link:{{navprefix}}/api-search-intercept[API intercept and data fetch requests] *** link:{{navprefix}}/custom-action-intro[Custom actions] **** link:{{navprefix}}/customize-actions[Custom actions through the UI] ***** link:{{navprefix}}/custom-action-url[URL actions] diff --git a/modules/ROOT/pages/embed-spotter.adoc b/modules/ROOT/pages/embed-spotter.adoc index 4a17680a9..b4a30edb6 100644 --- a/modules/ROOT/pages/embed-spotter.adoc +++ b/modules/ROOT/pages/embed-spotter.adoc @@ -175,26 +175,49 @@ image::./images/spotter3-leagcy-interface-automode.png[Spotter 3 interface] When Auto mode is enabled, **Preview data** and **Data Model instructions** options will not be available. ==== -==== Optional settings for Spotter 3 +==== New chat interface -Spotter 3 experience is available with a new prompt interface that includes additional features and user elements to enrich your Spotter experience. You can include the *Chat history* panel to allow your users to access the chat history from their previous sessions. +Spotter 3 experience is available with a new prompt interface that includes additional features and user elements to enrich your Spotter experience. -To enable the new chat interface and chat history features, set the `updatedSpotterChatPrompt` and `enablePastConversationsSidebar` attributes to `true`, as shown in this example: +To enable the new chat interface in your embed, set the `updatedSpotterChatPrompt` attribute: [source,JavaScript] ---- const spotterEmbed = new SpotterEmbed(document.getElementById('ts-embed'), { // ...other embed configuration attributes - // Enable the updated Spotter chat prompt experience. updatedSpotterChatPrompt: true, +}); +---- + +==== Chat history panel + +You can also include the *Chat history* panel to allow your users to access the chat history from their previous sessions. To enable chat history features, set the `enablePastConversationsSidebar` attributes to `true`. + +Additionally, you can customize the chat history sidebar using the settings available in the xref:SpotterSidebarViewConfig.adoc[SpotterSidebarViewConfig] interface and `spotterSidebarConfig` properties. + +//// +[source,JavaScript] +---- +const embed = new SpotterEmbed('#tsEmbed', { + // ...other embed view configuration options + // Configuration for the Spotter sidebar UI + spotterSidebarConfig: { + enablePastConversationsSidebar: true, // Show chat history sideabr + spotterSidebarTitle: 'My Conversations', // Set the title of the sidebar + spotterSidebarDefaultExpanded: true, // Expand Spotter chat history sidebar by default on load + }, +}) +---- +[source,JavaScript] +---- +const spotterEmbed = new SpotterEmbed(document.getElementById('ts-embed'), { // Enable the sidebar for accessing past Spotter conversations enablePastConversationsSidebar: true }); ---- - -The following figure shows UI elements and functions available in the new chat prompt and chat history panel: +//// [.widthAuto] [.bordered] diff --git a/modules/ROOT/pages/events-context-aware-routing.adoc b/modules/ROOT/pages/events-context-aware-routing.adoc new file mode 100644 index 000000000..bf7eda570 --- /dev/null +++ b/modules/ROOT/pages/events-context-aware-routing.adoc @@ -0,0 +1,396 @@ += HostEvents in multi‑modal contexts +:toc: true +:toclevels: 2 + +:page-title: HostEvents in multi‑modal contexts +:page-pageid: context-aware-event-routing +:page-description: Using the page context feature in the Visual Embed SDK, you can now implement context-aware routing of host events in multi-modal embedding experience + +This guide explains how developers embedding ThoughtSpot in their applications can use the page context stack, viewport visibility, and payload validation to achieve precise control and predictable handling of host events in a multi-modal experience. + +== Multi-modal experience +A multi‑modal experience in ThoughtSpot embedding refers to a navigation scenario where users interact with ThoughtSpot content through multiple UI layers, such as pages, dialogs, overlays, and other UI flows. For example: + +* An embedded Liveboard with multiple visualizations, each with its own menu and actions +* Spotter overlay on top of a Liveboard, with its own visualization and menu actions. +* Embedded visualizations with a Spotter overlay +* Embedded ThoughtSpot application experience with multiple pages, dialogs, or overlays + +In some of these scenarios, the same action will be presented in different UI layers to serve a specific context within the embedded application. For example, the embedded Liveboard, Answer, and Spotter pages include dialogs or overlays that expose similar actions such as save, edit, and download. + +== Host event behavior in multi-modal contexts + +Host events in single‑modal experiences, or on pages where actions and event targets are unique, typically do not cause unintended results. However, in multi‑modal contexts, host events such as `HostEvent.Edit` or `HostEvent.DownloadAsCsv` can lead to ambiguous results when multiple layers of embedded ThoughtSpot content are open at the same time. + +For example, if a user is on a Liveboard page and opens the **Spotter** dialog from a visualization tile, triggering `HostEvent.DownloadAsCsv` could invoke both the Liveboard and Spotter handlers. As a result, the user may see an unexpected dialog. + +For precise control and predictable event handling, developers can use the following options: + +* xref:events-context-aware-routing.adoc#_targeting_a_specific_visualization_using_vizid[Assign the event to a specific visualization using vizId] +* xref:events-context-aware-routing.adoc#_using_page_context_with_hostevents[Use the page context framework] for context-aware routing of host events. ++ +[NOTE] +==== +The page context framework is in beta and supported only in Visual Embed SDK v1.46.0 and later versions. Although you can fetch page context using the `getCurrentContext()` method, targeting a context in a host event is disabled by default. To enable this feature for your embedded application, contact your ThoughtSpot administrator. +==== + +== Using vizId to target a specific visualization +If a host event allows the `vizId` parameter, you can use it to target a specific visualization. For example, to trigger the *Edit* action on a specific visualization in an embedded Liveboard, you can specify the `vizId` parameter in the host event payload. + +In the following example, the host event triggers the **Edit** action on the specified visualization in a Liveboard embed: + +[source,JavaScript] +---- +// Import the HostEvent enum +import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + +// Trigger the 'Edit' action on a specific visualization within the embedded Liveboard. +liveboardEmbed.trigger(HostEvent.Edit, { + vizId: '730496d6-6903-4601-937e-2c691821af3c' // The GUID of the visualization to edit. +}); +---- + +If `vizId` is not specified, the edit action is triggered at the Liveboard level, instead of the visualization layer. + +In Spotter embed, `vizId` is a required parameter for several host events, including `HostEvent.Edit`. If it's not specified in the host event, the event trigger fails and results in an error indicating that the visualization context is missing. + +=== Visibility of visualizations in viewport +In a Liveboard embed, visualizations load incrementally as the user scrolls the Liveboard. Even if the Liveboard view is configured to load all visualizations simultaneously, the host events are triggered only on visualizations that are currently loaded and visible in the viewport. + +In the above example, if the visualization with the `730496d6-6903-4601-937e-2c691821af3c` `vizId` is not currently loaded and visible on the user’s screen, the host event will not trigger any action, indicating that the `vizId` is unknown or not currently loaded. + +== Using page context in host events +If the embedding app has multiple UI layers, developers can use the page context function and specify the context type in host events for precise control and predictable event handling. + +The page context framework in the SDK enables tracking the current navigation state of a user interaction within the embedded application. It provides the following information for context-based routing: + +* xref:events-context-aware-routing.adoc#_page_context_stack[Page context stack tracking] - For tracking the page, dialog, or overlay a user is viewing in the embedded application via the `getCurrentContext()` function. +* xref:events-context-aware-routing.adoc#[Context type] - For explicit targeting of host events + +=== Page context stack +ThoughtSpot maintains an ordered stack of active contexts for the embedded interfaces. Every page component or overlay registers its context when it mounts and unregisters it when unmounted. The current context is always at the top of the stack. + +==== Getting current context and page stack details +Developers can use the `getCurrentContext()` function to retrieve context details and apply host events accordingly. The `getCurrentContext()` method in the SDK allows retrieving information about the current page, open dialogs, and object IDs to determine the expected behavior and construct valid calls for event routing. + +The following example shows how to fetch the current context: + +[source,JavaScript] +---- +liveboardEmbed.getCurrentContext().then((ctx) => { + console.log('Current context:', ctx); +}); +---- + +The following example shows how to retrieve the current context and page stack details in a console log: + +[source,JavaScript] +---- +async function logCurrentContext() { + // Log the current (top-most) context object + const context = await embed.getCurrentContext(); + // - context.currentContext: the top (active) context + // - context.stack: the full page context stack + console.log('Current context:', context); +} +logCurrentContext(); +---- + +The available contexts are: + +* `liveboard` - The Liveboard layer +* `search` - Search data or the saved answer page. +* `spotter` - Spotter interface. +* `answer` - The Explore or Drill dialogs opened from a visualization or search Answer. +* `other` - Fallback for generic or app-level interactions not tied to a specific context. + +==== Page stack in an embedded Liveboard +The Visual Embed SDK maintains an ordered **page context stack** for each embedded interface. Every page component or overlay registers its context when it mounts and unregisters when it unmounts. + +The following examples show the page context transition as the user navigates to different UI layers: + +. **When a user opens a Liveboard** ++ +[source,JSON] +---- +{ + + "stack": [ + { + "name": "liveboard", + // Specifies the context type. For example, page, dialog + "type": "page", + // IDs for the Liveboard and its visualizations. + "objectIds": { + // Livenoard ID + "liveboardId": "lb-123", + // Array of visualization IDs visible in the Liveboard. + "vizIds": ["v1", "v2"] + } + } + ], + // Currently active page or view. + "currentContext": { + "name": "liveboard", + "type": "page", + "objectIds": { + "liveboardId": "lb-123", + "vizIds": ["v1", "v2"] + } + } +} +---- + +. **When a user opens the Spotter dialog on the Liveboard** ++ +When a user navigates to a Liveboard, and then opens Spotter as a dialog on top of it, and finally runs a search from Spotter that opens an answer, the page context stack changes as shown in this example: ++ +[source,JSON] +---- +{ + "stack": [ + { + // First entry in the stack: a Liveboard page + "name": "liveboard", + "type": "page", + "objectIds": { + "liveboardId": "lb-123", + "vizIds": ["v1","v2"] + } + }, + { + // Second entry: a Spotter dialog + "name": "spotter", //Spotter layer + "type": "dialog", // modal or overlay + "objectIds": { + "dataModelIds": ["ws-456"] //Data model ID used in Spotter. + } + } + ], + // Currently active layer + "currentContext": { + "name": "spotter", + "type": "dialog", + "objectIds": { + "dataModelIds": ["ws-456"] + } + } +} +---- + +. **When Spotter generates a visualization** ++ +[source,JSON] +---- +{ + "stack": [ + { + // Base page context: a Liveboard is open. + "name": "liveboard", + "type": "page", + "objectIds": { + "liveboardId": "lb-123", // ID of the Liveboard currently in view. + "vizIds": ["v1", "v2"] // IDs of visualizations available on this Liveboard. + } + }, + { + // Overlay context: Spotter layer is pened on top of the Liveboard page + "name": "spotter", + "type": "dialog", //Overlay above the page + "objectIds": { + "dataModelIds": ["ws-456"], // Data model IDs used by Spotter. + "vizIds": ["ans-1"] // IDs of visualizations in the Spotter layer. + } + } + ], + // Current active layer + "currentContext": { + "name": "spotter", + "type": "dialog", + "objectIds": { + "dataModelIds": ["ws-456"], + "vizIds": ["ans-1"] + } + } +} +---- + +. **When the user closes the Spotter dialog on a Liveboard** ++ +[source,JSON] +---- +{ + "stack": [ + { + // Liveboard page view + "name": "liveboard", + "type": "page", + "objectIds": { + "liveboardId": "lb-123", // GUID of the Liveboard in current view. + "vizIds": ["v1", "v2"] // IDs of visualizations visible in the Liveboard + } + } + ], + // Current active layer + "currentContext": { + "name": "liveboard", + "type": "page", + "objectIds": { + "liveboardId": "lb-123", + "vizIds": ["v1", "v2"] + } + } +} +---- + +=== Event trigger for single layer interaction +In embedded interfaces that allow single-layer interactions, event triggering can work without requiring explicit context definitions. The event is triggered automatically for the relevant interface element. For example, in Liveboard embedding, `HostEvent.SetVisibleVizs` is triggered at the Liveboard level to show a specific set of visualizations in the Liveboard view. + +[source,JavaScript] +---- +import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + +// Trigger a host event in the embedded Liveboard +liveboardEmbed.trigger( + HostEvent.SetVisibleVizs, // show only the specified visualizations + [ + 'viz1', // GUID of the first visualization + 'viz2', // GUID of the second visualization + ], +); +---- + +However, in full application embed, the same event (`HostEvent.SetVisibleVizs`) is routed to the active Liveboard layer in the user's current context. + +[source,JavaScript] +---- +import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + +// Trigger a host event in a full application embed +appEmbed.trigger( + HostEvent.SetVisibleVizs, // show only the specified visualizations + [ + 'viz1', // ID of the first visualization + 'viz2', // ID of the second visualization + ], +); +---- + +==== Routing events to the current context in real-time + +In a Liveboard embed, if the Spotter modal is open on top of a Liveboard layer, the current context changes to Spotter. If a matching handler exists in the Spotter layer, the SDK triggers the event. + +[source,JavaScript] +---- +import { HostEvent } from '@thoughtspot/visual-embed-sdk'; + +// Trigger search in Spotter by sending a query and executing search +spotterEmbed.trigger(HostEvent.SpotterSearch, { + query: 'revenue', + executeSearch: true, +}); + +// Trigger a CSV download for a specific Spotter visualization +liveboardEmbed.trigger(HostEvent.DownloadAsCsv, { + vizId: 'spotter-viz-id', // ID of the Spotter visualization/answer +}); +---- + +To automatically route events to the current context, you call `getCurrentContext()` on your embed instance to fetch the current context, and then use that information in your own routing logic before triggering a host event. + +The following example routes the download event only if the current context is Spotter in a Liveboard embed: + +[source,JavaScript] +---- +async function triggerDownloadForSpotter() { + // Fetch the current context + const context = await liveboardEmbed.getCurrentContext(); // returns ContextObject + //Trigger DownloadAsPng event if the current layer is Spotter, + if (context?.layer === 'Spotter') { + liveboardEmbed.trigger(HostEvent.DownloadAsCsv, { + vizId: context.vizId, + }); + } +} +---- + + +[IMPORTANT] +==== +* The host application cannot directly modify or inject context. It can only read the current context. The host application can trigger events such as updating filters or changing tabs, but the underlying page context is managed by the embedded component. +* Only the top-most (current) context is considered active for event routing. Actions cannot be triggered in inactive layers, unless explicitly targeted in the host event definition. +==== + +=== Specifying target context in host events +Developers can target an event subscription to a specific context, so that when the host application triggers an event, only the handler registered for that context is executed. This prevents the same action from being triggered multiple times across UI layers and overlays, and ensures that host events behave as users would expect in the product interface. + +To trigger the event only in a specific layer, specify the context explicitly in the host event. You can set the context type to one of the following values as required: + +* `ContextType.Liveboard` - For Liveboard page +* `ContextType.Search` - For Search data or the saved answer page. +* `ContextType.Spotter` - For the Spotter layer. +* `ContextType.Answer` - For the Explore or drill dialog opened from a visualization or search Answer. +* `ContextType.Other` - For generic or app-level events not tied to a specific context. + +In the following example, `HostEvent.Edit` is explicitly assigned to the Spotter context in a Liveboard embed: + +[source,JavaScript] +---- +import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; + +liveboardEmbed.trigger( + HostEvent.Edit, // Trigger Edit action + { + vizId: '730496d6-6903-4601-937e-2c691821af3c', //ID of the visualization + }, + // Route this event only to the Spotter context if the other UI layers in the stack have a matching handler. + ContextType.Spotter, +); +---- + +== Error handling +Your host events may return errors in the following scenarios: + +* When a matching handler is not present in the specified context. Verify the host event type, payload, and context type. +* If a required attribute is missing, for example, `vizId` in Spotter context. Add the `vizId`. +* Verify the visualizations and objects specified in the host event are present and visible in the embed view. If not, adjust the UI experience (scroll, navigate, open a dialog) before calling the host event. + +== Best practices and recommendations + +When building integrations that rely on context and app interactions via host events, consider the following recommendations: + +* Include type, data, and context in the event definition as appropriate: +** type - Host event type. For example, `HostEvent.Edit` +** data - Event payload with parameters for the host event execution. For example, `{ vizId?: string; ... }`. +** `context`: The UI context for event handling. For example, `ContextType.Liveboard`. + +* For visualization‑level actions, pass `vizId` in the host event payload to target a specific visualization. Ensure that the target visualization is rendered and visible in the viewport. +* For events where parameters such as `vizId` are optional, not specifying the parameter causes the action to apply to the current object in that embed context. For example, if `vizId` is not defined in `HostEvent.Pin`, the Pin modal is invoked for the Answer that the user is currently on. +* In Spotter embed, the `vizId` is a required parameter to trigger Pin, Download, Edit and other actions. If the required parameter is not specified in the event payload, the event trigger fails. +* For Liveboard or page‑level actions, use the event‑specific attributes such as `liveboardId`, `tabId`, and other fields instead of `vizId` where appropriate. For precise control, specify the context in the host event. +* To explicitly set context targets, you must first identify the context type. +When a context is specified, the event is routed to that context. ++ +If no context is defined and multiple UI layers with matching handlers are open, ThoughtSpot automatically routes the event to the current top-most active UI layer. ++ +The SDK executes handlers only for the resolved context. If the event is invalid for that context or no handler exists, it returns an error. + +//// +==== Listening to context changes +You can also listen to the context changes via EmbedEvent.EmbedPageContextChanged and retrieve the current context from a user session. + +[source,JavaScript] +---- +liveboardEmbed.on(EmbedEvent.EmbedPageContextChanged, (payload) => { +console.log("Current context:", payload.currentContext); +console.log("Full stack:", payload.stack); +}); +---- +//// + + +== Related resources + +* See xref:HostEvent.adoc[HostEvent] for a comprehensive list of host events available with the SDK. +* For information about triggering events on React components, see xref:https://developers.thoughtspot.com/docs/tutorials/react-components/lesson-04[Event listeners for React components]. + diff --git a/modules/ROOT/pages/rest-apiv2-changelog.adoc b/modules/ROOT/pages/rest-apiv2-changelog.adoc index 0cce110c0..f65785447 100644 --- a/modules/ROOT/pages/rest-apiv2-changelog.adoc +++ b/modules/ROOT/pages/rest-apiv2-changelog.adoc @@ -8,6 +8,43 @@ This changelog lists the features and enhancements introduced in REST API v2.0. For information about new features and enhancements available for embedded analytics, see xref:whats-new.adoc[What's New]. + +== Version 26.3.0.cl, March 2026 + +=== Webhook APIs + +The Webhook API allows configuring Amazon S3 buckets as storage destination for webhook payload delivery. + +* `/api/rest/2.0/webhooks/create` + +Configures storage destination for webhook delivery. +* `POST /api/rest/2.0/webhooks/{webhook_identifier}/update` + +Allows modifying storage configuration a webhook. +* `POST /api/rest/2.0/webhooks/search` + +Retrieves storage configuration details + +=== Object privilege APIs +Administrators and users with edit access to data models can now use the `/api/rest/2.0/security/metadata/manage-object-privilege` assign object-level permissions to users and groups and allow access to Spotter coaching information. + +To fetch object privileges for a data model, user, or group, use the `/api/rest/2.0/security/metadata/fetch-object-privileges` API endpoint. + +For more information, see xref:spotter-apis.adoc#_spotter_coaching_access[Spotter coaching access]. + +=== User API enhancements + +The user APIs now support setting browser language as the default locale for ThoughtSpot users. Administrators can set `use_browser_language` parameter as default locale for ThoughtSpot users during the following API operations: + +* When creating a new user via `POST /api/rest/2.0/users/create` + +* When importing users via `POST /api/rest/2.0/users/import` + +* When updating user preferences via `POST /api/rest/2.0/users/{user_identifier}/update` + + +When set to `true`, user's current locale preferences are overridden and the browser's language takes precedence. + +The status of browser language setting for a given user can also be retrieved using the following API endpoints: + +* `POST /api/rest/2.0/users/search` + +* `POST /api/rest/2.0/users/activate` + +* `GET /api/rest/2.0/auth/session/user` + + == Version 26.2.0.cl, February 2026 === Security settings APIs diff --git a/modules/ROOT/pages/spotter-apis.adoc b/modules/ROOT/pages/spotter-apis.adoc index cfcd7b5a2..82dfe53fb 100644 --- a/modules/ROOT/pages/spotter-apis.adoc +++ b/modules/ROOT/pages/spotter-apis.adoc @@ -1209,7 +1209,7 @@ The following example defines instructions to coach Spotter on how to interpret [source,cURL] ---- curl -X POST \ - --url 'https://https://{ThoughtSpot-Host}/api/rest/2.0/ai/instructions/set' \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/ai/instructions/set' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {AUTH_TOKEN} \ @@ -1251,7 +1251,7 @@ The following example shows the request body for retrieving NL instructions conf [source,cURL] ---- curl -X POST \ - --url 'https://https://{ThoughtSpot-Host}/api/rest/2.0/ai/instructions/set' \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/ai/instructions/set' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {AUTH_TOKEN} \ @@ -1278,6 +1278,102 @@ If the instructions are configured on the Model specified in the API request, Th } ---- +== Spotter coaching access + +ThoughtSpot supports publishing Spotter coaching information to other Orgs.Coaching changes from the primary Org are synchronized with the data models published in secondary Orgs. + +=== Assign Spotter coaching access privilege + +To allow users and groups to access spotter coaching information in data models, assign the `SPOTTER_COACHING_PRIVILEGE` via `POST` request to the `/api/rest/2.0/security/metadata/manage-object-privilege` API endpoint. + +In the API request, specify the data model, user, group, and ensure that the object privilege is set to `SPOTTER_COACHING_PRIVILEGE`. + +[source,cURL] +---- +curl -X POST \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/security/metadata/manage-object-privilege' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {AUTH_TOKEN}' \ + --data-raw '{ + "operation": "ADD", + "metadata_type": "LOGICAL_TABLE", + "object_privilege_types": [ + "SPOTTER_COACHING_PRIVILEGE" + ], + "metadata_identifiers": [ + "62f3e9b5-4fcc-4352-b8ad-fdddc2287506" + ], + "principals": [ + { + "identifier": "UserA", + "type": "USER" + } + ] +}' +---- + +If the request is successful, the API returns the 204 response code. + +=== Get a list of users or groups with Spotter coaching privilege + +To get a list of users and groups with the Spotter coaching privilege for a specific data model or all data models, send a `POST` request to the `/api/rest/2.0/security/metadata/fetch-object-privileges` API endpoint. + +In the API request, specify the user or the group for which you want to retrieve the object privilege details. + + +[source,cURL] +---- +curl -X POST \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/security/metadata/fetch-object-privileges' \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {AUTH_TOKEN}' \ + --data-raw '{ + "metadata": [ + { + "identifier": "53141e3b-331d-4a74-80a2-e4322f9d8339", + "type": "LOGICAL_TABLE" + } + ], + "record_offset": 0, + "record_size": 20, + "principals": [ + { + "identifier": "UserA", + "type": "USER" + } + ] +}' +---- + +If the request is successful, ThoughtSpot returns the object privilege details in the API response: + +[source,JSON] +---- +{ + "metadata_object_privileges":[ + { + "metadata_id":"53141e3b-331d-4a74-80a2-e4322f9d8339", + "metadata_name":"Model_formula_variable", + "metadata_type":"WORKSHEET", + "principal_object_privilege_info":[ + { + "principal_type":"USER", + "principal_object_privileges":[ + { + "principal_id":"0000083e-e253-10af-aa97-5e45d512ebbd", + "principal_name":"rani.gangwar@thoughtspot.com", + "principal_sub_type":"OIDC_USER", + "object_privileges":"[SPOTTER_COACHING_PRIVILEGE]" + } + ] + } + ] + } + ] +} +---- + == Additional resources * Visit the +++REST API v2.0 Playground+++ to view the API endpoints and verify the request and response workflows. diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index 9a0f677fd..47caf3b7f 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -8,6 +8,40 @@ This page lists new features, enhancements, and deprecated functionality in ThoughtSpot Embedded instances. +== Version 26.3.0.cl + +=== ThoughtSpot Webhook integration with Amazon S3 +You can now configure ThoughtSpot to deliver webhook payload and attachments directly into your own Amazon S3 storage using secure cross‑account access. To enable this integration, your AWS administrator must create an IAM Role and S3 permissions policy, and then register a webhook in ThoughtSpot to deliver the webhook payload and attachments directly to your S3 bucket. + +=== Host event enhancements for context-aware routing + +HostEvents in the Visual Embed SDK are enhanced to improve event routing and context targeting in ThoughtSpot embedded applications. + +Developers can use the page context framework in the SDK to route host events to a specific UI layer and align user experience with the product UI behavior in multi-modal contexts. + +For more information, see xref:events-context-aware-routing.adoc[ostEvents in multi‑modal contexts]. + +=== Spotter coaching access across published Orgs +Starting with 26.3.0.cl release, ThoughtSpot supports publishing Spotter coaching information to other Orgs. Coaching changes from the primary Org are synchronized with the data models published in secondary Orgs. + +Administrators and users with edit access to data models can programmatically control user access to Spotter coaching information using the object privilege REST API endpoint, `/api/rest/2.0/security/metadata/manage-object-privilege`. They can assign `SPOTTER_COACHING_PRIVILEGE` to other users and user groups, allowing access to the coaching information without requiring them to have data model editing or administration privileges. + +Users and groups with `SPOTTER_COACHING_PRIVILEGE` can import and export coaching TML on data models in the source and destination Orgs where the model is published, and can also share these objects with other users and groups. + +For more information, see xref:spotter-apis.adoc#_spotter_coaching_access[Spotter coaching access]. + +=== Spotter embed enhancements +Developers can customize the appearance of the chat history sidebar in the embedded Spotter 3 interface. + +For more information, see xref:embed-spotter.adoc#_chat_history_panel[Spotter embed documentation]. + +=== Visual Embed SDK +For information about the new features and enhancements introduced in Visual Embed SDK version 1.46.0, see the xref:api-changelog.adoc[Visual Embed changelog]. + +=== REST API v2 +For information about REST API v2 enhancements, see the xref:rest-apiv2-changelog.adoc[REST API v2.0 changelog]. + + == Version 26.2.0.cl === SpotterCode extension for IDEs [earlyAccess eaBackground]#Early Access# From 132efaeeceb7128f6264a883251ae00334a9b43f Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Thu, 26 Feb 2026 07:32:13 +0530 Subject: [PATCH 11/44] edits for 26.3 --- modules/ROOT/pages/api-changelog.adoc | 2 +- .../ROOT/pages/customize-nav-full-embed.adoc | 6 ++---- modules/ROOT/pages/full-embed.adoc | 6 ++---- modules/ROOT/pages/whats-new.adoc | 19 ++++++++++++++++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index 9699da074..e7a96d362 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -17,7 +17,7 @@ The SDK includes the following parameters, action IDs, and events to customizing Chat history sidebar customization:: -* `SpotterSidebarViewConfig` interface with configuration parameters for customizing the visibility and appearance of the chat history sidebar. +//* `SpotterSidebarViewConfig` interface with configuration parameters for customizing the visibility and appearance of the chat history sidebar. * `spotterSidebarConfig` properties for customizing the appearance and available options in the chat history sidebar. * Action IDs for customizing the visibility and status of the actions in the embedded Spotter interface: ** `Action.DataModelInstructions` for the data model instructions icon. diff --git a/modules/ROOT/pages/customize-nav-full-embed.adoc b/modules/ROOT/pages/customize-nav-full-embed.adoc index b66347f3f..e7012e98b 100644 --- a/modules/ROOT/pages/customize-nav-full-embed.adoc +++ b/modules/ROOT/pages/customize-nav-full-embed.adoc @@ -40,7 +40,6 @@ a| ** A hamburger icon for the sliding navigation overlay ** Object search bar ** Help and profile icons + -The user profile includes *Admin settings* option, which is visible to users with administration privileges. ** Org switcher * Left navigation ** A sliding left navigation panel controlled via the hamburger icon @@ -70,14 +69,13 @@ To show or hide the application switcher. | [tag greenBackground tick]#✓# Supported + In V2 experience, hides the app selector in the top navigation bar. | [tag greenBackground tick]#✓# Supported + -In the V3 experience, hides the app selection icons on the left navigation panel and the *Admin settings* option in the Profile menu. +In the V3 experience, hides the app selection icons on the left navigation panel. | `disableProfileAndHelp` + To show or hide the help and user profile icons in top navigation bar. | [tag greenBackground tick]#✓# Supported | [tag greenBackground tick]#✓# Supported + Also hides or shows *Help* menu on the left navigation panel of the home page. | [tag greenBackground tick]#✓# Supported + -Also hides the *Admin settings* menu in the profile dropdown. | `hideOrgSwitcher` + To show or hide the Org switcher. @@ -263,7 +261,7 @@ If you want to include the help menu and link:https://docs.thoughtspot.com/cloud By default, the help menu in the embedded view shows the legacy information center controlled using Pendo. To enable the new information center and add custom links, set `enablePendoHelp` to `false`. -To add custom links to the help menu, use the customization settings available on the **Admin settings** > **Help customization** page. For more information, refer to the link:https://docs.thoughtspot.com/cloud/latest/customize-help[ThoughtSpot Product Documentation]. +To add custom links to the help menu, use the customization options in the **Admin settings** > **Help customization** page. For more information, refer to the link:https://docs.thoughtspot.com/cloud/latest/customize-help[ThoughtSpot Product Documentation]. [source,JavaScript] ---- diff --git a/modules/ROOT/pages/full-embed.adoc b/modules/ROOT/pages/full-embed.adoc index 9bee3e046..02f4af538 100644 --- a/modules/ROOT/pages/full-embed.adoc +++ b/modules/ROOT/pages/full-embed.adoc @@ -6,16 +6,14 @@ :page-pageid: full-embed :page-description: You can embed full ThoughtSpot experience in your application and allow your users to create content for live analytics -Full app embedding allows you to embed the entire ThoughtSpot application or individual application pages in your app. - -Full app embedding provides access to all core ThoughtSpot features, along with additional customization options through the Visual Embed SDK, including custom actions and CSS styling rules. +Full app embedding allows you to embed the entire ThoughtSpot application or individual application pages in your app. It provides access to all core ThoughtSpot features, along with additional customization options through the Visual Embed SDK, including custom actions and CSS styling rules. The layout and feature set of the various pages in full app embedding are relatively fixed. If you require more granular control, use the embed components, such as xref:embed-pinboard.adoc[LiveboardEmbed], xref:embed-search.adoc[SearchEmbed], or xref:embed-spotter.adoc[SpotterEmbed] in the SDK. You can xref:page-navigation.adoc[control or customize navigation] within your web application using either full app or other embed components. [IMPORTANT] ==== * ThoughtSpot does not recommend mixing full application embedding with xref:embed-search.adoc[SearchEmbed] and xref:embed-pinboard.adoc[LiveboardEmbed] components. -* The *Develop* page and *Analyst Studio* option are not available in full application embed. +* The **Admin settings** and *Develop*, and *Analyst Studio* pages are not available in the full application embed. * To enable Spotter in the full application embed, set the home page search bar mode to `aiAnswer`. For more information, see xref:full-app-customize.adoc#_include_spotter_interface[Customize full application embedding]. ==== diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index 47caf3b7f..a581ddb0b 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -21,6 +21,19 @@ Developers can use the page context framework in the SDK to route host events to For more information, see xref:events-context-aware-routing.adoc[ostEvents in multi‑modal contexts]. +=== JWT-based ABAC implementation +The legacy JWT-based approach that uses `filter_rules` and `parameter_values` to implement Attribute-Based Access Control (ABAC) is deprecated. + +As part of this deprecation, the following changes have been introduced to the custom authentication token API workflow and REST API Playground: + +* The `filter_rules` parameter in the custom token authentication page in the REST API Playground is no longer be available for new configuration. This change doesn't affect your existing implementation. + +* The `parameter_values` property is not deprecated in the 26.3.0.cl release version and remains supported until further notice. However, using parameter values for row level security use cases will be ultimately be deprecated in an upcoming release. + +Existing ABAC implementations that use `filter_rules` will continue to function until further notice. However, we strongly recommend migrating your legacy ABAC implementation to the ABAC via RLS method that uses custom variables. For migration steps, refer to the ABAC migration guide. + +For new deployments, use ABAC via RLS with custom variables and pass data security attributes through the `variable_values` property in the custom access token, and define your RLS rules based on those variables. For more information, see xref:abac_rls-variables.adoc[ABAC via RLS]. + === Spotter coaching access across published Orgs Starting with 26.3.0.cl release, ThoughtSpot supports publishing Spotter coaching information to other Orgs. Coaching changes from the primary Org are synchronized with the data models published in secondary Orgs. @@ -35,13 +48,17 @@ Developers can customize the appearance of the chat history sidebar in the embe For more information, see xref:embed-spotter.adoc#_chat_history_panel[Spotter embed documentation]. +=== Full application embedding +The height and aspect ratio of the logo in the top-left corner of the ThoughtSpot application interface have been updated for visual alignment and consistency across pages. This enhancement is available only in the V3 navigation and home page experience. + +If you have embedded the full application with the V3 navigation experience, you may notice that the logo appears smaller in the top navigation. This is a design update and does not require any configuration changes to your current embedding implementation. However, we recommend that you review the logo appearance and adjust your custom logo if necessary. + === Visual Embed SDK For information about the new features and enhancements introduced in Visual Embed SDK version 1.46.0, see the xref:api-changelog.adoc[Visual Embed changelog]. === REST API v2 For information about REST API v2 enhancements, see the xref:rest-apiv2-changelog.adoc[REST API v2.0 changelog]. - == Version 26.2.0.cl === SpotterCode extension for IDEs [earlyAccess eaBackground]#Early Access# From 0f3746a9bee5f76146e31c8fae9a2c9432627fd6 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Thu, 26 Feb 2026 09:58:17 +0530 Subject: [PATCH 12/44] version number is banner --- src/configs/doc-configs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configs/doc-configs.js b/src/configs/doc-configs.js index 637c2ec28..c431ae85d 100644 --- a/src/configs/doc-configs.js +++ b/src/configs/doc-configs.js @@ -22,8 +22,8 @@ module.exports = { // 'https://developers.thoughtspot.com/docs/26.2.0.cl?pageid=whats-new' // - GA: ' /docs/whats-new' //linkHref: '/docs/whats-new', - linkHref: '/docs/26.2.0.cl?pageid=whats-new', - linkText: 'Version 26.2.0.cl', + linkHref: '/docs/26.3.0.cl?pageid=whats-new', + linkText: 'Version 26.3.0.cl', openInNewTab: true, }, TYPE_DOC_PREFIX: 'typedoc', From 9140538898fab46f3735fe1e0f05900c6fff1143 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Thu, 26 Feb 2026 13:48:39 +0530 Subject: [PATCH 13/44] edit --- modules/ROOT/pages/customize-links.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/customize-links.adoc b/modules/ROOT/pages/customize-links.adoc index a94e625c5..c4b185f00 100644 --- a/modules/ROOT/pages/customize-links.adoc +++ b/modules/ROOT/pages/customize-links.adoc @@ -217,7 +217,7 @@ https://www.mysite.com/{path} Link override settings allow embedded users to redirect native ThoughtSpot URLs to links within their host application. ThoughtSpot supports two Visual Embed SDK configurations for overriding links generated by ThoughtSpot. These settings work the same for multi-tenant ThoughtSpot embedded instances too. -You can set the `linkOverride` to `true` in the Visual Embed SDK to override the link format of your embedded application pages and navigation links. Once enabled, all links will display host application URLs when opened in a new tab. +You can set the `linkOverride` to `true` in the Visual Embed SDK to override the link format of your embedded application pages and navigation links. Once enabled, all links opened in a new tab via the right-click menu will show host application URLs. [source,JavaScript] From 1e3ac090ef8227aad5c7fe7d0b270e91aeede74b Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 01:32:49 +0530 Subject: [PATCH 14/44] webhook edits --- modules/ROOT/pages/common/nav.adoc | 3 +- modules/ROOT/pages/webhooks-lb-schedule.adoc | 42 +-- modules/ROOT/pages/webhooks-s3-storage.adoc | 339 +++++++++++++++++++ modules/ROOT/pages/webhooks.adoc | 5 + modules/ROOT/pages/whats-new.adoc | 4 +- 5 files changed, 363 insertions(+), 30 deletions(-) create mode 100644 modules/ROOT/pages/webhooks-s3-storage.adoc diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index 7fe2123f0..cdf26c325 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -213,8 +213,9 @@ include::generated/typedoc/CustomSideNav.adoc[] ** link:{{navprefix}}/v1v2-comparison[REST v1 and v2.0 comparison] ** link:{{navprefix}}/graphql-guide[GraphQL API ^Beta^] ** link:{{navprefix}}/webhooks[Webhooks] -*** link:{{navprefix}}/webhooks-kpi[Webhook for KPI alerts] *** link:{{navprefix}}/webhooks-lb-schedule[Webhook for Liveboard schedule events ^Beta^] +*** link:{{navprefix}}/webhooks-s3-integration[AWS S3 storage integration for Webhook delivery ^Beta^] +*** link:{{navprefix}}/webhooks-kpi[Webhook for KPI alerts] * MCP Servers and Tools ** link:{{navprefix}}/SpotterCode[SpotterCode for IDEs] diff --git a/modules/ROOT/pages/webhooks-lb-schedule.adoc b/modules/ROOT/pages/webhooks-lb-schedule.adoc index eec7e3d5a..fb92c8e30 100644 --- a/modules/ROOT/pages/webhooks-lb-schedule.adoc +++ b/modules/ROOT/pages/webhooks-lb-schedule.adoc @@ -335,6 +335,7 @@ If the registered webhook has Oauth authentication enabled, `Authorization: Bear |`signature_verification` + __Optional__ a| Signature verification parameters for the webhook endpoint to verify the authenticity of incoming requests. This typically involves ThoughtSpot signing the webhook payload with a secret, and your webhook endpoint validating this signature using the shared secret. + If using signature verification, specify the following parameters. * `type` + @@ -346,6 +347,13 @@ HTTP header where the signature is sent. Hash algorithm used for signature verification. * `secret` + Shared secret used for HMAC signature generation. +| `storage_destination` | Configuration for storage destination. Include the following attributes if you want to add destination storage to store the attachments delivered via a Webhook. + +* `storage_type` + +Type of storage destination. Currently, only the Amazon S3 Bucket is supported as the storage destination. +* `storage_config` + +Storage configuration parameters. If you have selected `AWS_S3`, specify the storage configuration parameters required for the Amazon S3 integration. For more information, see xref:webhooks-s3-storage.adoc[]. +|| |===== ===== Example request @@ -373,34 +381,7 @@ curl -X POST \ ===== Example response -If the webhook creation is successful, the API returns the following response: - -[source,JSON] ----- -{ - "id": "873dd4e2-6493-490d-a649-ba9ea66b11f5", - "name": "webhook-lb-event", - "description": "Webhook for Liveboard schedule", - "org": { - "id": "2100019165", - "name": "testOrg1" - }, - "url": "https://webhook.site/6643eba5-9d3e-42a1-85e0-bb686ba1524d", - "url_params": null, - "events": [ - "LIVEBOARD_SCHEDULE" - ], - "authentication": BEARER_TOKEN, - "signature_verification": null, - "creation_time_in_millis": 1761050197164, - "modification_time_in_millis": 1761050197164, - "created_by": { - "id": "08c6b203-ff6e-4ed8-b923-35ebbbfef27b", - "name": "UserA@UserA@example.com" - }, - "last_modified_by": null -} ----- +If the webhook creation is successful, the API returns 204 response code. ==== View webhook properties @@ -547,6 +528,11 @@ If the API request is successful, the API returns a 204 response code indicating To delete a webhook, send a `POST` request to the `/api/rest/2.0/webhooks/delete` endpoint. +[NOTE] +==== +When you delete a webhook with S3 storage, the webhook endpoint is removed and any events or workflows configured to use that webhook will no longer be able to deliver payloads. The files already stored in S3 are not deleted as part of webhook deletion; only the delivery mechanism will be removed. +==== + ===== Request parameters Specify the name or ID of the webhook to delete. diff --git a/modules/ROOT/pages/webhooks-s3-storage.adoc b/modules/ROOT/pages/webhooks-s3-storage.adoc new file mode 100644 index 000000000..b98e67596 --- /dev/null +++ b/modules/ROOT/pages/webhooks-s3-storage.adoc @@ -0,0 +1,339 @@ += Amazon S3 storage integration for Webhook delivery +:toc: true +:toclevels: 3 + +:page-title: Amazon S3 storage integration for Webhook delivery +:page-pageid: webhooks-s3-integration +:page-description: Configure Amazon S3 storage as destination for webhook payload delivery + +You can integrate ThoughtSpot with your Amazon Web Services (AWS) S3 storage to deliver webhook attachments directly to your S3 bucket using AWS cross-account access. + +When this integration is enabled, the webhook configured on your instance can deliver file attachments up to 25 MB directly to your Amazon S3 bucket. + +== Prerequisites + +Before you begin, review the following prerequisites and ensure that you have the required access and information to get started with the integration. + +* You have access to a ThoughtSpot instance. + +Ensure that the webhook feature is enabled on your instance and your ThoughtSpot user account has the administration or **Can Manage Webhooks** role privilege. +* You have an AWS account with the required permissions to create and manage S3 buckets, Identity and Access Management (IAM) role, and trust policies to allow ThoughtSpot to send data to your S3 bucket. +* You have a webhook listener endpoint that can accept `POST` requests with JSON payloads. + +== Getting started +To enable this integration, your AWS administrator must create an IAM role and configure the S3 permissions and IAM trust policy. ThoughtSpot administrators configure the webhook with the S3 storage destination. + +. xref:webhooks-s3-storage.adoc#_configuring_your_aws_s3_bucket_iam_role_and_policies[Configuring your AWS S3 bucket, IAM role, trust policies, and permissions] +. xref:webhooks-s3-storage.adoc#_configure_a_webhook_with_the_s3_storage_destination[Creating a Webhook with an S3 storage destination] in ThoughtSpot. +. xref:webhooks-s3-storage.adoc#_test_the_integration[Testing the integration] + +=== Configure your AWS S3 bucket, IAM role, and policies + +To set up your AWS S3 storage destination and to enable cross-account interaction, complete the following steps: + +* xref:webhooks-s3-storage.adoc#_create_an_s3_bucket[Create an S3 bucket] +* xref:webhooks-s3-storage.adoc#_create_an_iam_role[Create an IAM role] +* xref:webhooks-s3-storage.adoc#_attach_s3_permissions_policy[Attach S3 permissions policy] +* xref:webhooks-s3-storage.adoc#_provide_the_storage_configuration_information_to_thoughtspot[Provide storage configuration details to your ThoughtSpot administrator] + +==== Create an S3 bucket +To create an S3 bucket for your AWS account: + +. In the AWS console, go to **S3** > **Buckets**. +. Create a new bucket (recommended), or select an existing one. + +Use a dedicated S3 bucket or prefix for ThoughtSpot exports to enhance security and simplify auditing. +. Optionally, create a folder. For example, `thoughtspot/` to logically separate ThoughtSpot files. This serves as the S3 prefix for Webhook delivery. + +==== Create an IAM role +To create a cross-account IAM role: + +. Go to AWS Console → **IAM** > **Roles** > **Create role**. +. Select *Custom trust policy*. + +The IAM role’s trust policy must allow ThoughtSpot's AWS account to `sts:AssumeRole` to assume the role. The trust policy must also include an external ID and include the parameters shown in this example: ++ +[source,] +---- +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::111111111111:root" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "YOUR-EXTERNAL-ID" + } + } + } + ] +} +---- ++ +. Replace the following in the above example with appropriate values: + +* `111111111111` with the ThoughtSpot AWS Account ID provided to you. +* `YOUR-EXTERNAL-ID` with your chosen External ID (case‑sensitive). The External ID is a secret string you create. Use a unique, random value to prevent unauthorized access. + +. Give the role a descriptive name. For example, `thoughtspot-s3-upload`. + +==== Attach S3 permissions policy +In the role creation wizard: + +. Click **Add permissions**. +. Select **Create policy** +. Select the **JSON** tab and paste the policy text from the following example: ++ +[source,] +---- +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*" + } + ] +} +---- ++ +. Replace `YOUR-BUCKET-NAME` with the name of your S3 bucket. +. Optionally, to restrict to a specific folder/prefix, add the following: ++ +---- +"Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/thoughtspot/webhooks/*" +---- +. Save the policy and attach it to your IAM Role. +. Copy the Role ARN from the role’s Summary page; you need to provide this information to ThoughtSpot administrator. + +==== Provide the storage configuration information to ThoughtSpot +After the S3 bucket, IAM role, and trust policy are configured, provide the following information to your ThoughtSpot administrator: + +* IAM role ARN. For example, `arn:aws:iam::999888777666:role/thoughtspot-s3-upload` +* External ID. For example, `ts-webhook-x7k9m2p4q1` +* S3 bucket name. For example, `ts-data-exports` +* S3 region. For example, `us-east-1` +* S3 prefix for the folder (Optional). For example, `thoughtspot/`. + +=== Configure a Webhook with the S3 storage destination +To create a Webhook channel, the Webhook feature must be enabled on your instance. Users with administration or the**Can manage Webhooks** privilege can create a Webhook. + +To configure the S3 storage properties, you'll need the IAM role, external ID, S3 bucket name, and S3 region configured for your AWS cross-account. + +Currently, ThoughtSpot supports Webhook creation with AWS S3 storage via REST APIs only. + +==== Creating a Webhook +To create a webhook, send a `POST` request to the `/api/rest/2.0/webhooks/create` API endpoint. + +[NOTE] +==== +ThoughtSpot allows only one webhook per Org. +==== + +===== Request parameters + +[width="100%" cols="1,6"] +[options='header'] +|==== +|Parameter|Description +| `name` a|__String__. Name of the webhook. +| `description` + +__Optional__ a|__String__. Description text for the webhook. +| `url` a|__String__. The listening endpoint URL where the webhook payload will be sent. The payload will include metadata and a URL referencing the file stored in S3. +|`url_params` a|__Optional__. A JSON map of key-value pairs of parameters to add as a GET query params in the webhook URL. +| `events` a|__Array of strings__. List of events to subscribe to. Specify the event as `LIVEBOARD_SCHEDULE`. When this event is emitted, a webhook payload is initiated to send data to the configured S3 storage destination. +| `storage_destination` a| Configuration for storage destination. Specify the following parameters: +* `storage_type` + +__String__ Type of storage destination. Specify `AWS_S3`. +* `sstorage_config` + +Storage configuration parameters. Specify the storage configuration parameters from your AWS account administrator. + +* `bucket_name` + +__String__. S3 bucket name. For example, `ts-data-exports` +* `region`+ +__String__. S3 region. For example, `us-east-1` +* `role_arn` + +__String__. IAM role ARN. For example, `arn:aws:iam::999888777666:role/thoughtspot-s3-upload` +* `external_id` + +__String__. External ID. For example, `ts-webhook-x7k9m2p4q1` +* `path_prefix` __Optional__ + +__String__. S3 prefix for the folder For example, `thoughtspot/`. +|==== + +===== API request + +[source.cURL] +---- +curl -X POST \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/webhooks/create' \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {AUTH_TOKEN}' \ + --data-raw '{ + "name": "Webhook_S3", + "url": "https://api.example.com/thoughtspot/webhook", + "events": [ + "LIVEBOARD_SCHEDULE" + ], + "storage_destination": { + "storage_type": "AWS_S3", + "storage_config": { + "aws_s3_config": { + "bucket_name": "my-company-data-exports", + "region": "us-east-1 ", + "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", + "external_id": "ts-webhook-x7k9m2p4q1 ", + "path_prefix": "thoughtspot/" + } + } + } +}' +---- + +===== API response + +If the API request is successful, ThoughtSpot returns the Webhook configuration properties in the API response. + +[source,JSON] +---- +{ + "id":"e63886a8-d468-4fc8-9c9b-a72bab6c4aee", + "name":"name6", + "description":null, + "org":{ + "id":"0", + "name":"Primary" + }, + "url":"https://api.example.com/thoughtspot/webhook", + "url_params":{ + "key":"value" + }, + "events":[ + "LIVEBOARD_SCHEDULE" + ], + "authentication":null, + "signature_verification":null, + "creation_time_in_millis":1772100928376, + "modification_time_in_millis":1772100928376, + "created_by":{ + "id":"61b37746-a9bc-4433-b0cb-7a0e03225932", + "name":"demo_devuser" + }, + "last_modified_by":null, + "storage_destination":{ + "storage_type":"AWS_S3", + "storage_config":{ + "aws_s3_config":{ + "bucket_name":"ts-webhook-x7k9m2p4q1", + "region":"us-east-1", + "role_arn":"arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", + "external_id":"ts-webhook-x7k9m2p4q1", + "path_prefix":"thoughtspot/ " + } + } + } +} +---- + +==== Update the properties of a webhook + +To update the S3 storage details configured for a Webhook, send a `POST` request to the `/api/rest/2.0/webhooks/{webhook_identifier}/update` API endpoint. + +===== Request parameters + +Specify the `webhook_identifier` and pass it as a path parameter in the request URL. + +The API operation allows you to update the following webhook properties: + +* `name` + +Name of the webhook. +* `description` + +Description text of the webhook. +* `url` + +The URL of the webhook endpoint. +* `url_params` + +Query parameters to append to the endpoint URL. +* `events` + +Events subscribed to the webhook. In the current release, ThoughtSpot supports only the `LIVEBOARD_SCHEDULE` event. +* `storage_destination` + +S3 storage destination. +* `storage_type` + +Type of storage destination. Currently, only the Amazon S3 Bucket is supported as the storage destination. +* `storage_config` + +S3 Storage configuration parameters. + +===== API request + +The following example shows the request body for updating the name, description text, and endpoint URL of a webhook object: + +[source,cURL] +---- +curl -X POST \ + --url 'https://try-everywhere.thoughtspot.cloud/api/rest/2.0/webhooks/create' \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer {AUTH_TOKEN}' \ + --data-raw '{ + "name": "name6", + "url": "https://api.example.com/thoughtspot/webhook", + "events": [ + "LIVEBOARD_SCHEDULE" + ], + "url_params": { + "key": "value" + }, + "storage_destination": { + "storage_type": "AWS_S3", + "storage_config": { + "aws_s3_config": { + "bucket_name": "ts-webhook-x7k9m2p4q1", + "region": "us-east-1", + "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", + "external_id": "ts-webhook-x7k9m2p4q1", + "path_prefix": "thoughtspot/webhook " + } + } + } +}' +---- + +===== API response + +If the API request is successful, the API returns a 204 response code indicating a successful operation. + +=== Test the integration + +To test the integration: + +. Trigger a Liveboard Schedule event and verify if the webhook payload is initiated. +. Verify whether ThoughtSpot assumes your IAM role with the provided external ID. + +When ThoughtSpot calls `sts:AssumeRole` with your Role ARN and External ID, the AWS STS validates the request against your trust policy. If valid, AWS returns temporary credentials that are valid for 1 hour. + +. When ThoughtSpot is assuming the IAM role, and if you see the **Access Denied** error message: +* Verify whether the ThoughtSpot's Account ID in your trust policy is correct +* Check the external ID matches. External IDs are case-sensitive. +* Ensure the Role ARN you provided is correct. + +. If you see the **invalid external ID** error: + +* Verify whether the external ID is valid. Esnure that it does not include extra space or special characters. +* If the error persists, update the external ID both in your trust policy and ThoughtSpot. ++ +. If the access is denied when uploading to S3, check whether your permissions policy has the `s3:PutObject`. + + +* Verify the bucket name in the Resource ARN is correct. +* If using a prefix, ensure the Resource ARN includes it. +* Check whether your bucket has a restrictive bucket policy. ++ +ThoughtSpot can control which buckets can be accessed, what actions are permitted, and when to revoke access. However, ThoughtSpot cannot read files in your existing S3 storage that is not integrated with the webhook, access other buckets, list bucket contents, or delete any objects. + +== Additional resources + +* See also, xref:webhooks.adoc[Webhooks] and xref:webhooks-lb-schedule.adoc[Webhooks for Liveboard schedule events]. +* link:https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies-cross-account-resource-access.html[AWS IAM documentation] and link:https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_iam-s3.html[IAM and Amazon S3 troubleshooting guide]. + diff --git a/modules/ROOT/pages/webhooks.adoc b/modules/ROOT/pages/webhooks.adoc index 428635ae8..4cfe1226e 100644 --- a/modules/ROOT/pages/webhooks.adoc +++ b/modules/ROOT/pages/webhooks.adoc @@ -16,4 +16,9 @@ For more information, see xref:webhooks-kpi-alerts.adoc[Webhooks for KPI monitor Starting with 10.14.0.cl, ThoughtSpot supports using webhooks to send real-time notifications to external applications and communication channels when a Liveboard scheduled job is triggered. This enables you to automate workflows, send custom notifications, or integrate with other systems. To enable this feature, contact ThoughtSpot Support. + For information about how to configure communication channels and webhooks, see xref:webhooks-lb-schedule.adoc[Webhooks for Liveboard schedule events]. +== Storing Webhook attachments in Amazaon S3 bucket + +Starting with the ThoughtSpot 26.3.0.cl release, ThoughtSpot supports configuring an Amazon S3 bucket as the storage destination for delivering Webhook attachments via REST APIs. + +For more information, see xref:webhooks-s3-storage.adoc[]. diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index a581ddb0b..0ec31acdb 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -10,9 +10,11 @@ This page lists new features, enhancements, and deprecated functionality in Thou == Version 26.3.0.cl -=== ThoughtSpot Webhook integration with Amazon S3 +=== ThoughtSpot integration with Amazon S3 storage for webhook delivery You can now configure ThoughtSpot to deliver webhook payload and attachments directly into your own Amazon S3 storage using secure cross‑account access. To enable this integration, your AWS administrator must create an IAM Role and S3 permissions policy, and then register a webhook in ThoughtSpot to deliver the webhook payload and attachments directly to your S3 bucket. +For more information, see xref:webhooks-s3-storage.adoc[Amazon S3 storage integration for Webhook delivery]. + === Host event enhancements for context-aware routing HostEvents in the Visual Embed SDK are enhanced to improve event routing and context targeting in ThoughtSpot embedded applications. From 6d8f7b3ad091bd1a846d871d15e7711b945563f8 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 06:43:31 +0530 Subject: [PATCH 15/44] typo fixes --- modules/ROOT/pages/api-changelog.adoc | 38 ++++---- modules/ROOT/pages/embed-ai-analytics.adoc | 2 +- .../pages/events-context-aware-routing.adoc | 50 +++++------ modules/ROOT/pages/rest-apiv2-changelog.adoc | 56 ++++++------ modules/ROOT/pages/webhooks-lb-schedule.adoc | 90 ++++++++++--------- modules/ROOT/pages/webhooks-s3-storage.adoc | 71 +++++++-------- modules/ROOT/pages/whats-new.adoc | 30 +++---- 7 files changed, 170 insertions(+), 167 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index e7a96d362..36971c24f 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -13,29 +13,29 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For in [width="100%" cols="1,4"] |==== |[tag greenBackground]#NEW FEATURE# a| **Spotter experience** -The SDK includes the following parameters, action IDs, and events to customizing Spotter embed experience. +The SDK includes the following parameters, action IDs, and events to customize the Spotter embed experience. Chat history sidebar customization:: //* `SpotterSidebarViewConfig` interface with configuration parameters for customizing the visibility and appearance of the chat history sidebar. * `spotterSidebarConfig` properties for customizing the appearance and available options in the chat history sidebar. -* Action IDs for customizing the visibility and status of the actions in the embedded Spotter interface: +* Action IDs for customizing the visibility and status of actions in the embedded Spotter interface: ** `Action.DataModelInstructions` for the data model instructions icon. ** `Action.SpotterSidebarHeader` for the chat history sidebar header -** `Action.SpotterSidebarFooter` for the chat history sidebar header +** `Action.SpotterSidebarFooter` for the chat history sidebar footer ** `Action.SpotterSidebarToggle` for the chat history toggle that expands or collapses the sidebar. -** `Action.SpotterNewChat` for the new chat icon in the chat history sidebar -** `Action.SpotterPastChatBanner` for banner in the chat history sidebar -** `Action.SpotterChatMenu` for the chat menu component in the chat history sidebar +** `Action.SpotterNewChat` for the new chat icon in the chat history sidebar. +** `Action.SpotterPastChatBanner` for the banner in the chat history sidebar. +** `Action.SpotterChatMenu` for the chat menu component in the chat history sidebar. ** `Action.SpotterChatRename` for **Rename** action in the chat menu of a saved chat. -** `Action.SpotterChatDelete` for **Delete** action in the chat menu of a saved chat. +** `Action.SpotterChatDelete` for **Delete** action in the chat menu of a saved chat. //** `Action.SpotterDocs` for best practices documentation icon in the chat history sidebar. Events:: * `HostEvent.DataModelInstructions` + Opens the Data Model instructions modal. * `EmbedEvent.DataModelInstructions` + -Is emitted when a user clicks the Data model instructions icon in the Spotter interface. +Is emitted when a user clicks the Data Model instructions icon in the Spotter interface. * `EmbedEvent.SpotterConversationRenamed` + Is emitted when a user renames a saved chat. * `EmbedEvent.SpotterConversationDeleted` + @@ -57,9 +57,9 @@ The `dataPanelV2` parameter is deprecated and can no longer be used to switch be [width="100%" cols="1,4"] |==== -|[tag greenBackground]#NEW FEATURE# a|**Spotter enhancements ** +|[tag greenBackground]#NEW FEATURE# a| **Spotter enhancements** -You can now embed the Spotter 3 experience in your application and use features such as Auto mode for automatic data model selection, Chat history, and a new chat prompt interface. +You can now embed the Spotter 3 experience in your application and use features such as Auto mode for automatic data model selection, chat history, and a new chat prompt interface. * To enable the new chat prompt interface, set `updatedSpotterChatPrompt` to `true`. * To use Auto mode, set the `worksheetId` to `auto_mode`. @@ -82,7 +82,7 @@ On Spotter embed deployments running version 26.2.0.cl or later, the *Add to Coa Styling and grouping:: -* The `isLiveboardStylingAndGrouping` attribute, used for enabling the Liveboard styling and grouping feature, is now replaced with `isLiveboardMasterpiecesEnabled`. While your existing configuration with the deprecated attribute, `isLiveboardStylingAndGrouping` continues to work, we recommend switching to the new configuration setting. +* The `isLiveboardStylingAndGrouping` attribute, used to enable the Liveboard styling and grouping feature, is now replaced with `isLiveboardMasterpiecesEnabled`. While your existing configuration with the deprecated `isLiveboardStylingAndGrouping` attribute continues to work, we recommend switching to the new configuration setting. * The following action IDs are now available to show, disable, or hide the grouping menu actions on a Liveboard: ** `Action.MoveToGroup` for the **Move to Group** menu action. ** `Action.MoveOutOfGroup` for the **Move out of Group** menu action. @@ -96,21 +96,21 @@ For more information, see link:https://docs.thoughtspot.com/cloud/latest/securit + The `showMaskedFilterChip` setting is also available in full application embedding. -|[tag greenBackground]#NEW FEATURE# a|**Publishing objects** +|[tag greenBackground]#NEW FEATURE# a| **Publishing objects** The following action IDs are available for the data publishing menu actions in the *Data workspace* page: * `Action.Publish` for *Publish* -* `Action.ManagePublishing` for *Manange publishing* -* `Action.Unpublish` foe *Unpublish* +* `Action.ManagePublishing` for *Manage publishing* +* `Action.Unpublish` for *Unpublish* * `Action.Parameterize` for *Parameterize* |[tag greenBackground]#NEW FEATURE# a| **Error handling improvements** To handle errors in the embedding workflows, the SDK includes the following features: -* `ErrorDetailsTypes` enum for categorizing error types, such as `API`, `VALIDATION_ERROR`, `NETWORK` error. -* `EmbedErrorCodes` enum with specific error codes for programmatic error handling -* `EmbedErrorDetailsEvent` interface for structured error event handling +* `ErrorDetailsTypes` enum for categorizing error types, such as `API`, `VALIDATION_ERROR`, and `NETWORK`. +* `EmbedErrorCodes` enum with specific error codes for programmatic error handling. +* `EmbedErrorDetailsEvent` interface for structured error event handling. For more information, see link:https://developers.thoughtspot.com/docs/Enumeration_EmbedErrorCodes[EmbedErrorCodes] and link:https://developers.thoughtspot.com/docs/Enumeration_ErrorDetailsTypes[ErrorDetailsTypes]. |==== @@ -120,7 +120,7 @@ For more information, see link:https://developers.thoughtspot.com/docs/Enumerati [width="100%" cols="1,4"] |==== -|[tag redBackground]#DEPRECATED# | ** Use `minimumHeight` instead of `defaultHeight` ** + +|[tag redBackground]#DEPRECATED# | **Use `minimumHeight` instead of `defaultHeight`** + The `defaultHeight` parameter is deprecated in Visual Embed SDK v1.44.2 and later. To set the minimum height of the embed container for ThoughtSpot components such as a Liveboard, use the `minimumHeight` attribute instead. @@ -135,7 +135,7 @@ Allows configuring which API calls to intercept. * `interceptTimeout` + Sets the timeout duration for handling interception. * `isOnBeforeGetVizDataInterceptEnabled` + -When set to true, it enables the use of `EmbedEvent.OnBeforeGetVizDataIntercept` event to emit and intercept search execution calls initiated by the users and implement custom logic or workflow to allow or restrict search execution. +When set to true, it enables use of `EmbedEvent.OnBeforeGetVizDataIntercept` to emit and intercept search execution calls initiated by users and implement custom logic or workflows to allow or restrict search execution. * `EmbedEvent.ApiIntercept` + Emits when an API call matching the conditions defined in `interceptUrls` is detected. diff --git a/modules/ROOT/pages/embed-ai-analytics.adoc b/modules/ROOT/pages/embed-ai-analytics.adoc index c2abb742c..2d44838fd 100644 --- a/modules/ROOT/pages/embed-ai-analytics.adoc +++ b/modules/ROOT/pages/embed-ai-analytics.adoc @@ -47,7 +47,7 @@ __Available when the Spotter 2 experience is enabled on the instance__. **When to use**: + Use this version if you want to include agentic analytics and richer insights in your ThoughtSpot embedded app. -|Spotter Classic a| An early version of Spotter. +|Spotter Classic a| The early version of Spotter. **Key features and user experience**: + diff --git a/modules/ROOT/pages/events-context-aware-routing.adoc b/modules/ROOT/pages/events-context-aware-routing.adoc index bf7eda570..413362d46 100644 --- a/modules/ROOT/pages/events-context-aware-routing.adoc +++ b/modules/ROOT/pages/events-context-aware-routing.adoc @@ -4,7 +4,7 @@ :page-title: HostEvents in multi‑modal contexts :page-pageid: context-aware-event-routing -:page-description: Using the page context feature in the Visual Embed SDK, you can now implement context-aware routing of host events in multi-modal embedding experience +:page-description: Using the page context feature in the Visual Embed SDK, you can now implement context-aware routing of host events in a multi-modal embedding experience This guide explains how developers embedding ThoughtSpot in their applications can use the page context stack, viewport visibility, and payload validation to achieve precise control and predictable handling of host events in a multi-modal experience. @@ -22,12 +22,12 @@ In some of these scenarios, the same action will be presented in different UI la Host events in single‑modal experiences, or on pages where actions and event targets are unique, typically do not cause unintended results. However, in multi‑modal contexts, host events such as `HostEvent.Edit` or `HostEvent.DownloadAsCsv` can lead to ambiguous results when multiple layers of embedded ThoughtSpot content are open at the same time. -For example, if a user is on a Liveboard page and opens the **Spotter** dialog from a visualization tile, triggering `HostEvent.DownloadAsCsv` could invoke both the Liveboard and Spotter handlers. As a result, the user may see an unexpected dialog. +For example, if a user is on a Liveboard page and opens the **Spotter** dialog from a visualization tile, triggering `HostEvent.DownloadAsCsv` could invoke both the Liveboard and Spotter handlers. As a result, the user may see an unexpected dialog. For precise control and predictable event handling, developers can use the following options: * xref:events-context-aware-routing.adoc#_targeting_a_specific_visualization_using_vizid[Assign the event to a specific visualization using vizId] -* xref:events-context-aware-routing.adoc#_using_page_context_with_hostevents[Use the page context framework] for context-aware routing of host events. +* xref:events-context-aware-routing.adoc#_using_page_context_in_host_events[Use the page context framework] for context-aware routing of host events. + [NOTE] ==== @@ -52,7 +52,7 @@ liveboardEmbed.trigger(HostEvent.Edit, { If `vizId` is not specified, the edit action is triggered at the Liveboard level, instead of the visualization layer. -In Spotter embed, `vizId` is a required parameter for several host events, including `HostEvent.Edit`. If it's not specified in the host event, the event trigger fails and results in an error indicating that the visualization context is missing. +In Spotter embed, `vizId` is a required parameter for several host events, including `HostEvent.Edit`. If it is not specified in the host event, the event trigger fails and results in an error indicating that the visualization context is missing. === Visibility of visualizations in viewport In a Liveboard embed, visualizations load incrementally as the user scrolls the Liveboard. Even if the Liveboard view is configured to load all visualizations simultaneously, the host events are triggered only on visualizations that are currently loaded and visible in the viewport. @@ -62,13 +62,13 @@ In the above example, if the visualization with the `730496d6-6903-4601-937e-2c6 == Using page context in host events If the embedding app has multiple UI layers, developers can use the page context function and specify the context type in host events for precise control and predictable event handling. -The page context framework in the SDK enables tracking the current navigation state of a user interaction within the embedded application. It provides the following information for context-based routing: +The page context framework in the SDK enables tracking the current navigation state of a user interaction within the embedded application. It provides the following information for context-based routing: -* xref:events-context-aware-routing.adoc#_page_context_stack[Page context stack tracking] - For tracking the page, dialog, or overlay a user is viewing in the embedded application via the `getCurrentContext()` function. -* xref:events-context-aware-routing.adoc#[Context type] - For explicit targeting of host events +* xref:events-context-aware-routing.adoc#_page_context_stack[Page context stack tracking] - For tracking the page, dialog, or overlay a user is viewing in the embedded application via the `getCurrentContext()` function. +* xref:events-context-aware-routing.adoc#_specifying_target_context_in_host_events[Context type targeting] - For explicit targeting of host events. === Page context stack -ThoughtSpot maintains an ordered stack of active contexts for the embedded interfaces. Every page component or overlay registers its context when it mounts and unregisters it when unmounted. The current context is always at the top of the stack. +ThoughtSpot maintains an ordered stack of active contexts for embedded interfaces. Every page component or overlay registers its context when it mounts and unregisters it when it unmounts. The current context is always at the top of the stack. ==== Getting current context and page stack details Developers can use the `getCurrentContext()` function to retrieve context details and apply host events accordingly. The `getCurrentContext()` method in the SDK allows retrieving information about the current page, open dialogs, and object IDs to determine the expected behavior and construct valid calls for event routing. @@ -100,7 +100,7 @@ The available contexts are: * `liveboard` - The Liveboard layer * `search` - Search data or the saved answer page. -* `spotter` - Spotter interface. +* `spotter` - Spotter interface. * `answer` - The Explore or Drill dialogs opened from a visualization or search Answer. * `other` - Fallback for generic or app-level interactions not tied to a specific context. @@ -122,7 +122,7 @@ The following examples show the page context transition as the user navigates to "type": "page", // IDs for the Liveboard and its visualizations. "objectIds": { - // Livenoard ID + // Liveboard ID "liveboardId": "lb-123", // Array of visualization IDs visible in the Liveboard. "vizIds": ["v1", "v2"] @@ -160,10 +160,10 @@ When a user navigates to a Liveboard, and then opens Spotter as a dialog on top }, { // Second entry: a Spotter dialog - "name": "spotter", //Spotter layer + "name": "spotter", // Spotter layer "type": "dialog", // modal or overlay "objectIds": { - "dataModelIds": ["ws-456"] //Data model ID used in Spotter. + "dataModelIds": ["ws-456"] // Data model ID used in Spotter. } } ], @@ -194,9 +194,9 @@ When a user navigates to a Liveboard, and then opens Spotter as a dialog on top } }, { - // Overlay context: Spotter layer is pened on top of the Liveboard page + // Overlay context: Spotter layer is opened on top of the Liveboard page "name": "spotter", - "type": "dialog", //Overlay above the page + "type": "dialog", // Overlay above the page "objectIds": { "dataModelIds": ["ws-456"], // Data model IDs used by Spotter. "vizIds": ["ans-1"] // IDs of visualizations in the Spotter layer. @@ -260,7 +260,7 @@ liveboardEmbed.trigger( ); ---- -However, in full application embed, the same event (`HostEvent.SetVisibleVizs`) is routed to the active Liveboard layer in the user's current context. +However, in full application embed, the same event (`HostEvent.SetVisibleVizs`) is routed to the active Liveboard layer in the user's current context. [source,JavaScript] ---- @@ -298,17 +298,17 @@ liveboardEmbed.trigger(HostEvent.DownloadAsCsv, { To automatically route events to the current context, you call `getCurrentContext()` on your embed instance to fetch the current context, and then use that information in your own routing logic before triggering a host event. -The following example routes the download event only if the current context is Spotter in a Liveboard embed: +The following example routes the download event only if the current context is Spotter in a Liveboard embed: [source,JavaScript] ---- async function triggerDownloadForSpotter() { // Fetch the current context const context = await liveboardEmbed.getCurrentContext(); // returns ContextObject - //Trigger DownloadAsPng event if the current layer is Spotter, - if (context?.layer === 'Spotter') { + // Trigger DownloadAsCsv if the current layer is Spotter. + if (context?.currentContext?.name === 'spotter') { liveboardEmbed.trigger(HostEvent.DownloadAsCsv, { - vizId: context.vizId, + vizId: 'spotter-viz-id', }); } } @@ -326,10 +326,10 @@ Developers can target an event subscription to a specific context, so that when To trigger the event only in a specific layer, specify the context explicitly in the host event. You can set the context type to one of the following values as required: -* `ContextType.Liveboard` - For Liveboard page +* `ContextType.Liveboard` - For a Liveboard page. * `ContextType.Search` - For Search data or the saved answer page. * `ContextType.Spotter` - For the Spotter layer. -* `ContextType.Answer` - For the Explore or drill dialog opened from a visualization or search Answer. +* `ContextType.Answer` - For the Explore or Drill dialog opened from a visualization or search Answer. * `ContextType.Other` - For generic or app-level events not tied to a specific context. In the following example, `HostEvent.Edit` is explicitly assigned to the Spotter context in a Liveboard embed: @@ -341,7 +341,7 @@ import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk'; liveboardEmbed.trigger( HostEvent.Edit, // Trigger Edit action { - vizId: '730496d6-6903-4601-937e-2c691821af3c', //ID of the visualization + vizId: '730496d6-6903-4601-937e-2c691821af3c', // ID of the visualization }, // Route this event only to the Spotter context if the other UI layers in the stack have a matching handler. ContextType.Spotter, @@ -360,12 +360,12 @@ Your host events may return errors in the following scenarios: When building integrations that rely on context and app interactions via host events, consider the following recommendations: * Include type, data, and context in the event definition as appropriate: -** type - Host event type. For example, `HostEvent.Edit` +** type - Host event type. For example, `HostEvent.Edit`. ** data - Event payload with parameters for the host event execution. For example, `{ vizId?: string; ... }`. ** `context`: The UI context for event handling. For example, `ContextType.Liveboard`. * For visualization‑level actions, pass `vizId` in the host event payload to target a specific visualization. Ensure that the target visualization is rendered and visible in the viewport. -* For events where parameters such as `vizId` are optional, not specifying the parameter causes the action to apply to the current object in that embed context. For example, if `vizId` is not defined in `HostEvent.Pin`, the Pin modal is invoked for the Answer that the user is currently on. +* For events where parameters such as `vizId` are optional, not specifying the parameter causes the action to apply to the current object in that embed context. For example, if `vizId` is not defined in `HostEvent.Pin`, the Pin modal is invoked for the Answer the user is currently viewing. * In Spotter embed, the `vizId` is a required parameter to trigger Pin, Download, Edit and other actions. If the required parameter is not specified in the event payload, the event trigger fails. * For Liveboard or page‑level actions, use the event‑specific attributes such as `liveboardId`, `tabId`, and other fields instead of `vizId` where appropriate. For precise control, specify the context in the host event. * To explicitly set context targets, you must first identify the context type. @@ -392,5 +392,5 @@ console.log("Full stack:", payload.stack); == Related resources * See xref:HostEvent.adoc[HostEvent] for a comprehensive list of host events available with the SDK. -* For information about triggering events on React components, see xref:https://developers.thoughtspot.com/docs/tutorials/react-components/lesson-04[Event listeners for React components]. +* For information about triggering events on React components, see link:https://developers.thoughtspot.com/docs/tutorials/react-components/lesson-04[Event listeners for React components]. diff --git a/modules/ROOT/pages/rest-apiv2-changelog.adoc b/modules/ROOT/pages/rest-apiv2-changelog.adoc index f65785447..0919c8276 100644 --- a/modules/ROOT/pages/rest-apiv2-changelog.adoc +++ b/modules/ROOT/pages/rest-apiv2-changelog.adoc @@ -13,17 +13,17 @@ This changelog lists the features and enhancements introduced in REST API v2.0. === Webhook APIs -The Webhook API allows configuring Amazon S3 buckets as storage destination for webhook payload delivery. +The Webhook API allows configuring Amazon S3 buckets as a storage destination for webhook payload delivery. * `/api/rest/2.0/webhooks/create` + Configures storage destination for webhook delivery. * `POST /api/rest/2.0/webhooks/{webhook_identifier}/update` + -Allows modifying storage configuration a webhook. +Allows modifying storage configuration for a webhook. * `POST /api/rest/2.0/webhooks/search` + -Retrieves storage configuration details +Retrieves storage configuration details. === Object privilege APIs -Administrators and users with edit access to data models can now use the `/api/rest/2.0/security/metadata/manage-object-privilege` assign object-level permissions to users and groups and allow access to Spotter coaching information. +Administrators and users with edit access to data models can now use `/api/rest/2.0/security/metadata/manage-object-privilege` to assign object-level permissions to users and groups and allow access to Spotter coaching information. To fetch object privileges for a data model, user, or group, use the `/api/rest/2.0/security/metadata/fetch-object-privileges` API endpoint. @@ -31,15 +31,15 @@ For more information, see xref:spotter-apis.adoc#_spotter_coaching_access[Spotte === User API enhancements -The user APIs now support setting browser language as the default locale for ThoughtSpot users. Administrators can set `use_browser_language` parameter as default locale for ThoughtSpot users during the following API operations: +The user APIs now support setting browser language as the default locale for ThoughtSpot users. Administrators can set the `use_browser_language` parameter as the default locale for ThoughtSpot users during the following API operations: * When creating a new user via `POST /api/rest/2.0/users/create` + * When importing users via `POST /api/rest/2.0/users/import` + * When updating user preferences via `POST /api/rest/2.0/users/{user_identifier}/update` + -When set to `true`, user's current locale preferences are overridden and the browser's language takes precedence. +When set to `true`, a user's current locale preference is overridden and the browser's language takes precedence. -The status of browser language setting for a given user can also be retrieved using the following API endpoints: +The status of the browser language setting for a given user can also be retrieved using the following API endpoints: * `POST /api/rest/2.0/users/search` + * `POST /api/rest/2.0/users/activate` + @@ -53,7 +53,7 @@ This release introduces the following Security settings APIs: * `POST /api/rest/2.0/system/security-settings/configure` + Allows configuring security settings at the Org level or for all Orgs on a ThoughtSpot instance. * `POST /api/rest/2.0/system/security-settings/search` + -Gets a list of security settings configured on specific Org or for all Orgs on a ThoughtSpot instance. +Gets a list of security settings configured on a specific Org or for all Orgs on a ThoughtSpot instance. For more information, see xref:security-settings.adoc[Security Settings]. @@ -91,12 +91,12 @@ The `include_variable_values` parameter in the API request allows including vari === Spotter APIs -This release introduces the following Spotter APIs +This release introduces the following Spotter APIs: * `POST /api/rest/2.0/ai/instructions/set` + -Allows configuring natural language (NL) instructions on a Model to coach the Spotter on how to interpret queries, handle data nuances, and improve responses. +Allows configuring natural language (NL) instructions on a model to coach Spotter on how to interpret queries, handle data nuances, and improve responses. * `POST /api/rest/2.0/ai/instructions/get` + -Gets NL instructions that are currently assigned to a Model. +Gets NL instructions that are currently assigned to a model. * `POST /api/rest/2.0/ai/data-source-suggestions` + Retrieves a list of recommended data sources based on the specified query string. @@ -118,23 +118,23 @@ For more information, see xref:abac-user-parameters.adoc[ABAC via tokens]. === New API endpoints System:: -This release introduces the following endpoints for configuring communication channel preference. +This release introduces the following endpoints for configuring communication channel preferences. * `POST /api/rest/2.0/system/preferences/communication-channels/configure` [beta betaBackground]^Beta^ + -Sets a communication channel preference for all Orgs at the cluster level or at the indiviudal Org level. +Sets a communication channel preference for all Orgs at the cluster level or at the individual Org level. * `POST /api/rest/2.0/system/preferences/communication-channels/search` [beta betaBackground]^Beta^ + Gets details of the communication channel preferences configured on ThoughtSpot. + -For more information, see xref:webhooks-lb-schedule.adoc#_configure_a_webhook_communication_channel_for_the_liveboard_schedule_event[Configure communication channel settings]. +For more information, see xref:webhooks-lb-schedule.adoc#_configure_a_webhook_communication_channel[Configure communication channel settings]. Webhook:: The following APIs are introduced for webhook CRUD operations: * `POST /api/rest/2.0/webhooks/create` Creates a webhook. * `POST /api/rest/2.0/webhooks/{webhook_identifier}/update` -Updates the properties of a webhook +Updates the properties of a webhook. * `POST /api/rest/2.0/webhooks/search` -Gets a list of webhooks configured in ThoughtSpot or a specific Org in ThoughtSpot +Gets a list of webhooks configured in ThoughtSpot or in a specific Org. * `POST /api/rest/2.0/webhooks/delete` Deletes the webhook. + @@ -176,14 +176,14 @@ To update the properties of a specific variable, use the `/api/rest/2.0/template === User API enhancements -The following APIs now support `variable_values` parameter. The `variable_values` property can be used for user-specific customization. +The following APIs now support the `variable_values` parameter. The `variable_values` property can be used for user-specific customization. * `POST /api/rest/2.0/users/create` * `POST /api/rest/2.0/users/search` * `POST /api/rest/2.0/users/activate` === DBT API enhancements -The `/api/rest/2.0/dbt/generate-tml` endpoint supports `model_tables` attribute to allow listing Models and its Tables. +The `/api/rest/2.0/dbt/generate-tml` endpoint supports the `model_tables` attribute to list models and their tables. //// @@ -591,7 +591,7 @@ Report API:: The `POST /api/rest/2.0/report/answer` API endpoint supports downloading an Answer generated by the Spotter AI APIs: -* `session_identifier` + +* `session_identifier` + Session ID returned in API response by the `/api/rest/2.0/ai/answer/create` or `/api/rest/2.0/ai/conversation/create` endpoint. * `generation_number` + Number assigned to the Answer session with Spotter. @@ -612,7 +612,7 @@ Use `POST /api/rest/2.0/connections/{connection_identifier}/update` and `POST /a Authentication:: -The `user_parameters` property in `/api/rest/2.0/auth/token/full` and `/api/rest/2.0/auth/token/object` APIs is deprecated. +The `user_parameters` property in `/api/rest/2.0/auth/token/full` and `/api/rest/2.0/auth/token/object` APIs is deprecated. + ThoughtSpot recommends using `/api/rest/2.0/auth/token/custom` API endpoint with `filter_rules` and `parameter_values` to configure user properties for ABAC via tokens. @@ -667,7 +667,7 @@ The following API endpoints available for data connections: Authentication API:: -* `/api/rest/2.0/auth/token/validate` + +* `/api/rest/2.0/auth/token/validate` + Validates the authentication token of the logged-in user. TML API:: @@ -706,7 +706,7 @@ The `/api/rest/2.0/roles/create` and `/api/rest/2.0/roles/{role_identifier}/upda DBT:: -You can now use `file_content` to upload DBT Manifest and Catalog artifact files as a ZIP file in your API requests to the `/api/rest/2.0/dbt/dbt-connection`, `/api/rest/2.0/dbt/generate-tml`, `/api/rest/2.0/dbt/generate-sync-tml`, and `/api/rest/2.0/dbt/update-dbt-connection` endpoints. Required if the `import_type` parameter is set to `'ZIP_FILE`. +You can now use `file_content` to upload DBT Manifest and Catalog artifact files as a ZIP file in your API requests to the `/api/rest/2.0/dbt/dbt-connection`, `/api/rest/2.0/dbt/generate-tml`, `/api/rest/2.0/dbt/generate-sync-tml`, and `/api/rest/2.0/dbt/update-dbt-connection` endpoints. This field is required if the `import_type` parameter is set to `'ZIP_FILE'`. Connections:: @@ -734,7 +734,7 @@ The `trigger_activation_email` property allows you to specify if an activation e Version Control APIs:: -The following parameters in the `/api/rest/2.0/vcs/git/config/create` and `/api/rest/2.0/vcs/git/config/update` are deprecated from 9.10.5.cl onwards: +The following parameters in `/api/rest/2.0/vcs/git/config/create` and `/api/rest/2.0/vcs/git/config/update` are deprecated from 9.10.5.cl onward: * `default_branch_name` + Replaced by `commit_branch_name` @@ -751,10 +751,10 @@ DBT:: * `POST /api/rest/2.0/dbt/dbt-connection` + Creates a DBT connection. -* `POST /api/rest/2.0/dbt/generate-tml` + +* `POST /api/rest/2.0/dbt/generate-tml` + Generates Worksheets and Tables for a given DBT connection. * `POST /api/rest/2.0/dbt/generate-sync-tml` + -Synchronizes the existing TML of data models and Worksheets and import them to Thoughtspot. +Synchronizes the existing TML of data models and Worksheets and imports them to ThoughtSpot. * `POST /api/rest/2.0/dbt/search` + Gets a list of DBT connection objects for a given user or Org. * `POST /api/rest/2.0/dbt/{dbt_connection_identifier}` + @@ -896,7 +896,7 @@ For more information, see xref:version_control.adoc[Git integration and version === Response code change [tag redBackground]#BREAKING CHANGE# -The following endpoints now return the 204 response code instead of 200. The 204 code doesn't return a response body. This change may affect your current implementation, so we recommend that you update your code to avoid issues. +The following endpoints now return the 204 response code instead of 200. The 204 code does not return a response body. This change may affect your current implementation, so we recommend that you update your code to avoid issues. * `POST /api/rest/2.0/connection/delete` * `POST /api/rest/2.0/connection/update` @@ -938,7 +938,7 @@ Gets a list of Liveboard jobs configured on a ThoughtSpot instance * `*POST* /api/rest/2.0/schedules/{schedule_identifier}/delete` + Deletes a scheduled job. -For more information, see link:{{navprefix}}/restV2-playground?apiResourceId=http/api-endpoints/schedules/search-schedule[REST API v2.0 Reference]. +For more information, see link:{{navprefix}}/restV2-playground?apiResourceId=http%2Fapi-endpoints%2Fschedules%2Fsearch-schedule[REST API v2.0 Reference]. === API to fetch authentication token @@ -958,7 +958,7 @@ The `GET /api/rest/2.0/auth/session/token` API endpoint fetches the current auth == Version 9.3.0.cl, June 2023 -The following Version Control [beta betaBackground]^Beta^ API endpoints are now available for the lifecycle management of content on your deployment environments: +The following Version Control [beta betaBackground]^Beta^ API endpoints are now available for the lifecycle management of content on your deployment environments: * `*POST* /api/rest/2.0/vcs/git/config/search` * `*POST* /api/rest/2.0/vcs/git/commits/search` diff --git a/modules/ROOT/pages/webhooks-lb-schedule.adoc b/modules/ROOT/pages/webhooks-lb-schedule.adoc index fb92c8e30..0cba8e56b 100644 --- a/modules/ROOT/pages/webhooks-lb-schedule.adoc +++ b/modules/ROOT/pages/webhooks-lb-schedule.adoc @@ -1,18 +1,20 @@ -= Webhooks for Liveboard schedule events [beta betaBackground]^Beta^ += Webhooks for Liveboard schedule events :toc: true :toclevels: 3 -:page-title: Webhooks for Liveboard Schedueld Jobs +:page-title: Webhooks for Liveboard Scheduled Jobs :page-pageid: webhooks-lb-schedule :page-description: Configure Webhooks and send alerts to specific communication channels +[beta betaBackground]Beta + To provide flexibility and programmatic control for users who want to customize notifications and automate workflows based on Liveboard scheduling events, ThoughtSpot provides the ability to configure a webhook communication channel. By configuring a webhook, users can send notifications automatically to a target application whenever a scheduled Liveboard job is triggered in ThoughtSpot. Webhook support for Liveboard schedule events is available only on ThoughtSpot Cloud instances running 10.14.0.cl or later. This feature is currently in beta and is not enabled by default. To enable this feature on your instance, contact ThoughtSpot Support. == Overview -If you have scheduled a Liveboard job to receive a daily report via email, you can configure ThoughtSpot to send the report directly to a webhook endpoint and create your own custom emails or workflow. +If you have scheduled a Liveboard job to receive a daily report via email, you can configure ThoughtSpot to send the report directly to a webhook endpoint and create your own custom emails or workflows. To automate sending scheduled Liveboard notifications to a webhook endpoint, the following configuration is required: @@ -36,10 +38,10 @@ In the current release: ==== == Get started -The webhooks setup for Liveboard Schedule events involves the following steps: +The webhook setup for Liveboard schedule events includes the following steps: -* xref:webhooks-lb-schedule.adoc#_configure_webhook_communication_channel[Configuring a webhook communication channel at the cluster or Org level]. -* xref:webhooks-lb-schedule.adoc#_configure_a_webhook_for_liveboard_schedule_event[Creating a webhook to listen to the Liveboard schedule events]. +* xref:webhooks-lb-schedule.adoc#_configure_a_webhook_communication_channel[Configuring a webhook communication channel at the instance-level or for a specific Org level]. +* xref:webhooks-lb-schedule.adoc#_configure_a_webhook[Creating a webhook to listen to Liveboard schedule events]. * xref:webhooks-lb-schedule.adoc#_verify_the_webhook_payload[Verifying the webhook payload]. === Before you begin @@ -51,14 +53,14 @@ If your instance has Role-based Access Control (RBAC) enabled, you need the foll ** `APPLICATION_ADMINISTRATION` (*Can Manage Application settings*) to create and view communication channels. ** `CAN_MANAGE_WEBHOOKS` (*Can manage webhooks*) to create and manage webhooks. * Ensure that the REST APIs for setting communication channel preference and configuring webhooks are enabled on your instance. If the APIs are not available on your instance, contact ThoughtSpot Support. -* To allow outbound traffic from the ThoughtSpot to the webhook endpoint, add the webhook destination URL to the xref:security-settings.adoc#csp-connect-src[CSP connect-src] allowlist in ThoughtSpot. +* To allow outbound traffic from ThoughtSpot to the webhook endpoint, add the webhook destination URL to the xref:security-settings.adoc#csp-connect-src[CSP connect-src] allowlist in ThoughtSpot. * Ensure that your destination application has a callback URL to accept HTTP POST requests from ThoughtSpot. * If you plan to use OAuth authentication, make sure you have the OAuth credentials and authorization URL of your application. * If you plan to use an API key for authentication, ensure that you have a valid API key. === Configure a webhook communication channel -To create a webhook communication channel for the Liveboard Schedule event, use the channel preference REST API. +To create a webhook communication channel for the Liveboard schedule event, use the channel preference REST API. ==== Create a webhook communication channel @@ -70,12 +72,12 @@ To create the webhook communication channel and set messaging preferences, send [options='header'] |===== |Parameter|Description | -.3+| `cluster_preferences` 2+|__Array of strings__. Sets default preferences for all Orgs in the instance. You must specify the following parameters: +.3+| `cluster_preferences` 2+|__Array of objects__. Sets default preferences for all Orgs in the instance. You must specify the following parameters: -|`event_type` -a|__String__. Type of the event for which communication channels are configured. For Liveboard schedule event, set the parameter value as `LIVEBOARD_SCHEDULE`. +| `event_type` +a|__String__. Type of the event for which communication channels are configured. For the Liveboard schedule event, set the parameter value as `LIVEBOARD_SCHEDULE`. -|`channels` a| +| `channels` a| __Array of strings__. Communication channel for the event type specified in the request. Valid values are: + * `EMAIL` @@ -83,15 +85,15 @@ __Array of strings__. Communication channel for the event type specified in the To create a webhook channel for the Liveboard schedule event, specify `WEBHOOK`. -.5+| `org_preferences` 2+|__Array of strings__. By default, preferences configured at the cluster level will apply to all Orgs in the instance. To override the default preferences for your Org, set the Org-specific preferences: +.5+| `org_preferences` 2+|__Array of objects__. By default, preferences configured at the cluster level apply to all Orgs in the instance. To override default preferences for your Org, set Org-specific preferences: | `org_identifier` a| __String__. Name or ID of the Org. | `preferences` a| -__Array of strings__. Define the following parameters to set communication channel preferences for the Org. If the preferences are not set, the Org will inherit the default preferences applied at the cluster level. +__Array of objects__. Define the following parameters to set communication channel preferences for the Org. If preferences are not set, the Org inherits the default preferences applied at the cluster level. * `event_type` + -__String__. Type of the event for which communication channels are configured. For Liveboard schedule event, set the parameter value as `LIVEBOARD_SCHEDULE`. +__String__. Type of the event for which communication channels are configured. For the Liveboard schedule event, set the parameter value as `LIVEBOARD_SCHEDULE`. * `channels` + __Array of strings__. Communication channel for the event type specified in the request. Valid values are: + ** `EMAIL` @@ -103,9 +105,9 @@ To set up a webhook channel for the Liveboard schedule event, specify `WEBHOOK`. | `operation` a|__String__. Type of operation. The following options are available: ** `REPLACE` - To replace default preferences. -** `RESET` - To restore default preferences. For reset operation, you'll also need to specify the event type. Note that this operation will remove any Org-specific overrides and restores the default preferences configured at the cluster level. +** `RESET` - To restore default preferences. For reset operation, you'll also need to specify the event type. Note that this operation removes any Org-specific overrides and restores the default preferences configured at the cluster level. -|`reset_events` a|__Array of strings__. For RESET operations, specify the event type to reset. Note that the reset operation removes Org-specific configuration for the events specified in `reset_events`. +| `reset_events` a|__Array of strings__. For `RESET` operations, specify the event type to reset. Note that the reset operation removes Org-specific configuration for the events specified in `reset_events`. ||| |===== @@ -313,11 +315,11 @@ To create a webhook for the Liveboard schedule event, send a `POST` request to t |Parameter|Description | `name` a|__String__. Name of the webhook. | `description` + -__Optional__ a|__String__. Description text for the webhook +__Optional__ a|__String__. Description text for the webhook. | `url` a|__String__. The fully qualified URL of the listening endpoint where the webhook payload will be sent. The webhook endpoint to which you want to send notifications. -|`url_params` a| A JSON map of key-value pairs of parameters to add as a GET query params in the webhook URL. +| `url_params` a| A JSON map of key-value pairs to append as query parameters in the webhook URL. | `events` a|__Array of strings__. List of events to subscribe to. Specify the event as `LIVEBOARD_SCHEDULE`. -|`authentication` a| +| `authentication` a| Defines authentication method and credentials that ThoughtSpot will use when sending HTTP requests to the webhook endpoint. @@ -331,8 +333,8 @@ Authentication methods with username and password. Authentication token to authenticate and authorize requests. * `OAUTH2` + OAuth credentials to authorize API requests. Specify client ID, client secret key, and authorization URL. -If the registered webhook has Oauth authentication enabled, `Authorization: Bearer ` is sent in the request header. -|`signature_verification` + +If the registered webhook has OAuth authentication enabled, `Authorization: Bearer ` is sent in the request header. +| `signature_verification` + __Optional__ a| Signature verification parameters for the webhook endpoint to verify the authenticity of incoming requests. This typically involves ThoughtSpot signing the webhook payload with a secret, and your webhook endpoint validating this signature using the shared secret. @@ -347,7 +349,7 @@ HTTP header where the signature is sent. Hash algorithm used for signature verification. * `secret` + Shared secret used for HMAC signature generation. -| `storage_destination` | Configuration for storage destination. Include the following attributes if you want to add destination storage to store the attachments delivered via a Webhook. +| `storage_destination` | Configuration for the storage destination. Include the following attributes if you want to add destination storage for attachments delivered via a webhook. * `storage_type` + Type of storage destination. Currently, only the Amazon S3 Bucket is supported as the storage destination. @@ -374,20 +376,20 @@ curl -X POST \ ], "authentication": { "BEARER_TOKEN": "Bearer {AUTH_TOKEN}" - } + }, "description": "Webhook for Liveboard schedule" }' ---- ===== Example response -If the webhook creation is successful, the API returns 204 response code. +If webhook creation is successful, the API returns a 204 response code. ==== View webhook properties To view the properties of a webhook or get a list of webhooks configured on your ThoughtSpot instance, send a `POST` request to the `/api/rest/2.0/webhooks/search` API endpoint. -To get specific information, define the following parameters. If the API request is sent without any parameters in the request body, ThoughtSpot returns the webhooks configured for the Org contexts in ThoughtSpot. +To get specific information, define the following parameters. If the API request is sent without parameters in the request body, ThoughtSpot returns the webhooks configured for the Org context in ThoughtSpot. ===== Request parameters @@ -399,7 +401,7 @@ To get specific information, define the following parameters. If the API request __Optional__ |__String__. ID or name of the Org. | `webhook_identifier` + __Optional__ | __String__. ID or name of the webhook. -|`event_type` + +| `event_type` + __Optional__| __String__. Type of webhook event to filter by. For Liveboard schedule events, specify `LIVEBOARD_SCHEDULE`. |Pagination settings a| If fetching multiple records, specify the following parameters to paginate API response: + @@ -408,7 +410,7 @@ __Integer__. Specifies the starting point (index) from which records should be r * `record_size` + __Integer__. Specifies the number of records to return in the response. Default is 50. | `sort_options` + -__Optional__| Enables sorting of the API response by a specific field in ascending or descending order. Specify the `field_name` and define the desired sort order. +__Optional__| Enables sorting of the API response by a specific field in ascending or descending order. Specify the `field_name` and define the desired sort order. | |===== @@ -496,7 +498,7 @@ Query parameters to append to the endpoint URL. * `events` + Events subscribed to the webhook. In the current release, ThoughtSpot supports only the `LIVEBOARD_SCHEDULE` event. * `authentication` + -Authentication method and credentials that ThoughtSpot will use when sending HTTP requests to the webhook endpoint +Authentication method and credentials that ThoughtSpot will use when sending HTTP requests to the webhook endpoint. * `signature_verification` + Signature verification parameters for the webhook endpoint to verify the authenticity of incoming requests. @@ -530,7 +532,7 @@ To delete a webhook, send a `POST` request to the `/api/rest/2.0/webhooks/delete [NOTE] ==== -When you delete a webhook with S3 storage, the webhook endpoint is removed and any events or workflows configured to use that webhook will no longer be able to deliver payloads. The files already stored in S3 are not deleted as part of webhook deletion; only the delivery mechanism will be removed. +When you delete a webhook with S3 storage, the webhook endpoint is removed and any events or workflows configured to use that webhook can no longer deliver payloads. Files already stored in S3 are not deleted as part of webhook deletion; only the delivery mechanism is removed. ==== ===== Request parameters @@ -540,7 +542,7 @@ Specify the name or ID of the webhook to delete. [options='header'] |===== |Parameter|Description -|`webhook_identifiers` |__Array of strings__. ID of name of the webhooks to delete. +| `webhook_identifiers` |__Array of strings__. ID or name of the webhooks to delete. || |===== @@ -600,13 +602,13 @@ If the API request is successful, the webhook is deleted, and the API returns th === Verify the webhook payload -After a webhook channel is configured for Liveboard schedule events and a webhook is created for these events at the Org level, it's applied to all Liveboard schedules in an Org. +After a webhook channel is configured for Liveboard schedule events and a webhook is created for these events at the Org level, it is applied to all Liveboard schedules in an Org. When a Liveboard schedule event is triggered based on the conditions defined in the schedule, the webhook sends the payload with the following schema to the configured endpoint. Based on the Liveboard job settings, the payload includes metadata properties such as webhook communication channel ID, recipient details, Liveboard schedule details, event properties, and a link to the Liveboard. For testing purposes, you can use a URL from the link:https://webhook.site/[Webhook site, window=_blank] as a webhook endpoint and check the payload when the Liveboard schedule event is triggered. -==== Contents of the webhook playload +==== Contents of the webhook payload The Webhook payload uses a specific schema structure that determines the contents of the payload delivered to the webhook endpoint. The payload contains metadata about the event, the source, the actor, the target object, and event-specific data. The payload is typically sent as a form field named `payload` in a `multipart/form-data` request, with optional file attachments. @@ -638,16 +640,16 @@ The `WebhookPayload` schema defines the structure for webhook event notification | `timestamp` | string | Timestamp of when the event occurred. | Yes | `eventType` | string | Type of event that triggered the webhook payload. For example, `LIVEBOARD_SCHEDULE`. | Yes | `schemaVersion` | string | Schema version. | Yes -| `source` | object |Source endpoint that triggered the event. Includes the parameters defined in the xref:webhooks-lb-schedule.adoc#_webhooksourceinfo[WebhookSourceInfo] schema. | Yes +| `source` | object | Source endpoint that triggered the event. Includes the parameters defined in the xref:webhooks-lb-schedule.adoc#_webhooksourceinfo[WebhookSourceInfo] schema. | Yes | `actor` | object | Actor that initiated the event. For more information, see xref:webhooks-lb-schedule.adoc#_webhookactorinfo[WebhookActorInfo]. | Yes | `metadataObject` | object | Metadata object details. For more information, see xref:webhooks-lb-schedule.adoc#_webhooktargetobjectinfo[WebhookTargetObjectInfo]. | Yes -| `data` | object |Data specific to the Liveboard schedule event. For more information, see xref:webhooks-lb-schedule.adoc#_liveboardscheduledata[LiveboardScheduleData]. | Yes +| `data` | object | Data specific to the Liveboard schedule event. For more information, see xref:webhooks-lb-schedule.adoc#_liveboardscheduledata[LiveboardScheduleData]. | Yes |||| |===== ===== WebhookSourceInfo -The `WebhookSourceInfo` schema defines the properties of source application instance that triggered the webhook event. +The `WebhookSourceInfo` schema defines the properties of the source application instance that triggered the webhook event. [width="100%" cols="1,1,3,1"] [options='header'] @@ -671,9 +673,9 @@ The `WebhookActorInfo` schema defines the properties of the entity that initiate | Field | Type | Description | Required? | `actorType` | string | Initiator of the event such as the API client or user. The default actor type is `SYSTEM`. | Yes -| `id` | string a| Unique identifier such as GUID or object ID).For system-generated responses, the `id` will be set as `null`. | No +| `id` | string a| Unique identifier, such as a GUID or object ID. For system-generated responses, the `id` is set to `null`. | No | `name` | string a| Name of the actor that initiated the event. For system-generated responses, the `name` will be set as `null`. | No -| `email` | string a| Email of the actor that initiated the event. For system-generated responses, the `name` will be set as `null`. | No +| `email` | string a| Email of the actor that initiated the event. For system-generated responses, the `email` is set to `null`. | No |||| |===== @@ -695,7 +697,7 @@ The `WebhookTargetObjectInfo` schema defines the object for which the event is g ===== LiveboardScheduleData -The `WebhookTargetObjectInfo` schema defines event-specific data for Liveboard schedule events, including schedule details, recipients, and additional context. +The `LiveboardScheduleData` schema defines event-specific data for Liveboard schedule events, including schedule details, recipients, and additional context. [width="100%" cols="1,1,3,1"] [options='header'] @@ -703,8 +705,8 @@ The `WebhookTargetObjectInfo` schema defines event-specific data for Liveboard s | Field | Type | Description | Required | `scheduleDetails` | object | Details of the Liveboard schedule that triggered the event. This includes the schedule ID, object type, and output format. For more information, see xref:webhooks-lb-schedule.adoc#_scheduledetails[ScheduleDetails]. | Yes -| `recipients` | array | Details of the ThoughtSpot users, groups, and email addresses of the external users who are configured as subscribers of the Liveboard schedule notifications and recipients of the webhook payload. For more information, xref:webhooks-lb-schedule.adoc#_recipientinfo[RecipientInfo]. | Yes -| `viewInfo` | object | Information about the Liveboard view. Applicable if the Liveboard Schedule event is triggered for a personalized view of the Liveboard. For more information, xref:webhooks-lb-schedule.adoc#_viewinfo[ViewInfo]. | No +| `recipients` | array | Details of the ThoughtSpot users, groups, and email addresses of external users configured as subscribers to Liveboard schedule notifications and recipients of the webhook payload. For more information, see xref:webhooks-lb-schedule.adoc#_recipientinfo[RecipientInfo]. | Yes +| `viewInfo` | object | Information about the Liveboard view. Applicable if the Liveboard schedule event is triggered for a personalized view of the Liveboard. For more information, see xref:webhooks-lb-schedule.adoc#_viewinfo[ViewInfo]. | No | `aiHighlights` | string | AI Highlights information. Applicable if AI highlights feature is enabled for the visualizations on the Liveboard. | No | `msgUniqueId` | string | Unique message identifier. Unique ID of the webhook payload message. This ID can be used for traceability and deduplication on the receiving end. | No | `channelID` | string | The communication channel ID used for event dissemination. | No @@ -726,12 +728,12 @@ The `ScheduleDetails` schema defines the properties of the schedule that trigger | `creationTime` | string | Timestamp of when the schedule was created. | No | `description` | string | Description of the schedule. | No | `authorId` | string | ID of the user that scheduled the Liveboard job. | No -| `viewInfo` | object | Information about the Liveboard view. Applicable if the Liveboard Schedule event is triggered for a personalized view of the Liveboard. For more information, xref:webhooks-lb-schedule.adoc#_viewinfo[ViewInfo]. | No +| `viewInfo` | object | Information about the Liveboard view. Applicable if the Liveboard schedule event is triggered for a personalized view of the Liveboard. For more information, see xref:webhooks-lb-schedule.adoc#_viewinfo[ViewInfo]. | No | `userIds` | array | IDs of the ThoughtSpot users that are subscribed to the scheduled Liveboard notifications. | No | `groupIds` | array | IDs of the ThoughtSpot groups that are subscribed to the scheduled Liveboard notifications.| No | `runId` | string | Schedule run ID of the Liveboard job. | No | `exportRequest` | object | Details of the file export request. If the scheduled notification includes PDF attachment, the `exportRequest` includes details of the Liveboard and PDF page attributes. | No -| `fileFormat` | string | File format for export. The schedule notification generally include PDF attachments. | No +| `fileFormat` | string | File format for export. Schedule notifications generally include PDF attachments. | No | `status` | string | Status of the schedule. | No | `emailIds` | array | Email IDs of users subscribed to Liveboard job schedule. | No |||| @@ -751,7 +753,7 @@ The `RecipientInfo` schema defines the object properties of the recipients of th * `USER` - For ThoughtSpot users * `EXTERNAL_EMAIL` - For external recipients | Yes -| `id` | string | IDs of the ThoughtSpot user and groups that are subscribed to the Liveboard schedule. | No +| `id` | string | IDs of ThoughtSpot users and groups that are subscribed to the Liveboard schedule. | No | `name` | string | Name of the recipient. | No | `email` | string | Email address of the recipient. | Yes | `locale`| string | Locale of the recipient. For example, `en_US`. | No diff --git a/modules/ROOT/pages/webhooks-s3-storage.adoc b/modules/ROOT/pages/webhooks-s3-storage.adoc index b98e67596..65488889f 100644 --- a/modules/ROOT/pages/webhooks-s3-storage.adoc +++ b/modules/ROOT/pages/webhooks-s3-storage.adoc @@ -10,19 +10,19 @@ You can integrate ThoughtSpot with your Amazon Web Services (AWS) S3 storage to When this integration is enabled, the webhook configured on your instance can deliver file attachments up to 25 MB directly to your Amazon S3 bucket. -== Prerequisites +== Before you begin Before you begin, review the following prerequisites and ensure that you have the required access and information to get started with the integration. * You have access to a ThoughtSpot instance. + -Ensure that the webhook feature is enabled on your instance and your ThoughtSpot user account has the administration or **Can Manage Webhooks** role privilege. +Ensure that the webhook feature is enabled on your instance and your ThoughtSpot user account has the administration or *Can Manage Webhooks* role privilege. * You have an AWS account with the required permissions to create and manage S3 buckets, Identity and Access Management (IAM) role, and trust policies to allow ThoughtSpot to send data to your S3 bucket. * You have a webhook listener endpoint that can accept `POST` requests with JSON payloads. == Getting started To enable this integration, your AWS administrator must create an IAM role and configure the S3 permissions and IAM trust policy. ThoughtSpot administrators configure the webhook with the S3 storage destination. -. xref:webhooks-s3-storage.adoc#_configuring_your_aws_s3_bucket_iam_role_and_policies[Configuring your AWS S3 bucket, IAM role, trust policies, and permissions] +. xref:webhooks-s3-storage.adoc#_configure_your_aws_s3_bucket_iam_role_and_policies[Configuring your AWS S3 bucket, IAM role, trust policies] . xref:webhooks-s3-storage.adoc#_configure_a_webhook_with_the_s3_storage_destination[Creating a Webhook with an S3 storage destination] in ThoughtSpot. . xref:webhooks-s3-storage.adoc#_test_the_integration[Testing the integration] @@ -41,14 +41,14 @@ To create an S3 bucket for your AWS account: . In the AWS console, go to **S3** > **Buckets**. . Create a new bucket (recommended), or select an existing one. + Use a dedicated S3 bucket or prefix for ThoughtSpot exports to enhance security and simplify auditing. -. Optionally, create a folder. For example, `thoughtspot/` to logically separate ThoughtSpot files. This serves as the S3 prefix for Webhook delivery. +. Optionally, create a folder, for example, `thoughtspot/`, to logically separate ThoughtSpot files. This serves as the S3 prefix for webhook delivery. ==== Create an IAM role To create a cross-account IAM role: -. Go to AWS Console → **IAM** > **Roles** > **Create role**. +. Go to the AWS Console > **IAM** > **Roles** > **Create role**. . Select *Custom trust policy*. + -The IAM role’s trust policy must allow ThoughtSpot's AWS account to `sts:AssumeRole` to assume the role. The trust policy must also include an external ID and include the parameters shown in this example: +The IAM role's trust policy must allow ThoughtSpot's AWS account to call `sts:AssumeRole`. The trust policy must also include an external ID and the parameters shown in this example: + [source,] ---- @@ -74,7 +74,7 @@ The IAM role’s trust policy must allow ThoughtSpot's AWS account to `sts:Assum . Replace the following in the above example with appropriate values: * `111111111111` with the ThoughtSpot AWS Account ID provided to you. -* `YOUR-EXTERNAL-ID` with your chosen External ID (case‑sensitive). The External ID is a secret string you create. Use a unique, random value to prevent unauthorized access. +* `YOUR-EXTERNAL-ID` with your chosen External ID (case-sensitive). The External ID is a secret string you create. Use a unique, random value to prevent unauthorized access. . Give the role a descriptive name. For example, `thoughtspot-s3-upload`. @@ -82,7 +82,7 @@ The IAM role’s trust policy must allow ThoughtSpot's AWS account to `sts:Assum In the role creation wizard: . Click **Add permissions**. -. Select **Create policy** +. Select **Create policy**. . Select the **JSON** tab and paste the policy text from the following example: + [source,] @@ -109,7 +109,7 @@ In the role creation wizard: "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/thoughtspot/webhooks/*" ---- . Save the policy and attach it to your IAM Role. -. Copy the Role ARN from the role’s Summary page; you need to provide this information to ThoughtSpot administrator. +. Copy the Role ARN from the role's Summary page; you need to provide this information to your ThoughtSpot administrator. ==== Provide the storage configuration information to ThoughtSpot After the S3 bucket, IAM role, and trust policy are configured, provide the following information to your ThoughtSpot administrator: @@ -118,14 +118,14 @@ After the S3 bucket, IAM role, and trust policy are configured, provide the foll * External ID. For example, `ts-webhook-x7k9m2p4q1` * S3 bucket name. For example, `ts-data-exports` * S3 region. For example, `us-east-1` -* S3 prefix for the folder (Optional). For example, `thoughtspot/`. +* S3 prefix for the folder (optional). For example, `thoughtspot/`. === Configure a Webhook with the S3 storage destination -To create a Webhook channel, the Webhook feature must be enabled on your instance. Users with administration or the**Can manage Webhooks** privilege can create a Webhook. +To create a webhook channel, the webhook feature must be enabled on your instance. Users with administration privileges or the *Can Manage Webhooks* privilege can create a webhook. -To configure the S3 storage properties, you'll need the IAM role, external ID, S3 bucket name, and S3 region configured for your AWS cross-account. +To configure the S3 storage properties, you'll need the IAM role, external ID, S3 bucket name, and S3 region configured for your AWS cross-account setup. -Currently, ThoughtSpot supports Webhook creation with AWS S3 storage via REST APIs only. +Currently, ThoughtSpot supports webhook creation with AWS S3 storage via REST APIs only. ==== Creating a Webhook To create a webhook, send a `POST` request to the `/api/rest/2.0/webhooks/create` API endpoint. @@ -145,35 +145,36 @@ ThoughtSpot allows only one webhook per Org. | `description` + __Optional__ a|__String__. Description text for the webhook. | `url` a|__String__. The listening endpoint URL where the webhook payload will be sent. The payload will include metadata and a URL referencing the file stored in S3. -|`url_params` a|__Optional__. A JSON map of key-value pairs of parameters to add as a GET query params in the webhook URL. +| `url_params` a|__Optional__. A JSON map of key-value pairs to append as query parameters in the webhook URL. | `events` a|__Array of strings__. List of events to subscribe to. Specify the event as `LIVEBOARD_SCHEDULE`. When this event is emitted, a webhook payload is initiated to send data to the configured S3 storage destination. | `storage_destination` a| Configuration for storage destination. Specify the following parameters: * `storage_type` + -__String__ Type of storage destination. Specify `AWS_S3`. -* `sstorage_config` + +__String__ Type of storage destination. Specify `AWS_S3`. + + +* `storage_config` + Storage configuration parameters. Specify the storage configuration parameters from your AWS account administrator. * `bucket_name` + __String__. S3 bucket name. For example, `ts-data-exports` -* `region`+ +* `region` + __String__. S3 region. For example, `us-east-1` * `role_arn` + __String__. IAM role ARN. For example, `arn:aws:iam::999888777666:role/thoughtspot-s3-upload` * `external_id` + __String__. External ID. For example, `ts-webhook-x7k9m2p4q1` * `path_prefix` __Optional__ + -__String__. S3 prefix for the folder For example, `thoughtspot/`. +__String__. S3 prefix for the folder. For example, `thoughtspot/`. |==== ===== API request -[source.cURL] +[source,cURL] ---- curl -X POST \ --url 'https://{ThoughtSpot-Host}/api/rest/2.0/webhooks/create' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer {AUTH_TOKEN}' \ + -H 'Authorization: Bearer {AUTH_TOKEN}' \ --data-raw '{ "name": "Webhook_S3", "url": "https://api.example.com/thoughtspot/webhook", @@ -185,9 +186,9 @@ curl -X POST \ "storage_config": { "aws_s3_config": { "bucket_name": "my-company-data-exports", - "region": "us-east-1 ", - "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", - "external_id": "ts-webhook-x7k9m2p4q1 ", + "region": "us-east-1", + "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload", + "external_id": "ts-webhook-x7k9m2p4q1", "path_prefix": "thoughtspot/" } } @@ -231,9 +232,9 @@ If the API request is successful, ThoughtSpot returns the Webhook configuration "aws_s3_config":{ "bucket_name":"ts-webhook-x7k9m2p4q1", "region":"us-east-1", - "role_arn":"arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", + "role_arn":"arn:aws:iam::999888777666:role/thoughtspot-s3-upload", "external_id":"ts-webhook-x7k9m2p4q1", - "path_prefix":"thoughtspot/ " + "path_prefix":"thoughtspot/" } } } @@ -274,7 +275,7 @@ The following example shows the request body for updating the name, description [source,cURL] ---- curl -X POST \ - --url 'https://try-everywhere.thoughtspot.cloud/api/rest/2.0/webhooks/create' \ + --url 'https://{ThoughtSpot-Host}/api/rest/2.0/webhooks/{webhook_identifier}/update' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {AUTH_TOKEN}' \ @@ -293,9 +294,9 @@ curl -X POST \ "aws_s3_config": { "bucket_name": "ts-webhook-x7k9m2p4q1", "region": "us-east-1", - "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload ", + "role_arn": "arn:aws:iam::999888777666:role/thoughtspot-s3-upload", "external_id": "ts-webhook-x7k9m2p4q1", - "path_prefix": "thoughtspot/webhook " + "path_prefix": "thoughtspot/webhook" } } } @@ -310,21 +311,21 @@ If the API request is successful, the API returns a 204 response code indicating To test the integration: -. Trigger a Liveboard Schedule event and verify if the webhook payload is initiated. +. Trigger a Liveboard schedule event and verify that the webhook payload is initiated. . Verify whether ThoughtSpot assumes your IAM role with the provided external ID. + -When ThoughtSpot calls `sts:AssumeRole` with your Role ARN and External ID, the AWS STS validates the request against your trust policy. If valid, AWS returns temporary credentials that are valid for 1 hour. +When ThoughtSpot calls `sts:AssumeRole` with your Role ARN and External ID, AWS STS validates the request against your trust policy. If valid, AWS returns temporary credentials that are valid for 1 hour. . When ThoughtSpot is assuming the IAM role, and if you see the **Access Denied** error message: -* Verify whether the ThoughtSpot's Account ID in your trust policy is correct +* Verify whether the ThoughtSpot account ID in your trust policy is correct. * Check the external ID matches. External IDs are case-sensitive. * Ensure the Role ARN you provided is correct. -. If you see the **invalid external ID** error: +. If you see the **invalid external ID** error: -* Verify whether the external ID is valid. Esnure that it does not include extra space or special characters. +* Verify whether the external ID is valid. Ensure that it does not include extra spaces or special characters. * If the error persists, update the external ID both in your trust policy and ThoughtSpot. + -. If the access is denied when uploading to S3, check whether your permissions policy has the `s3:PutObject`. + +. If access is denied when uploading to S3, check whether your permissions policy has `s3:PutObject`. + * Verify the bucket name in the Resource ARN is correct. * If using a prefix, ensure the Resource ARN includes it. @@ -334,6 +335,6 @@ ThoughtSpot can control which buckets can be accessed, what actions are permitte == Additional resources -* See also, xref:webhooks.adoc[Webhooks] and xref:webhooks-lb-schedule.adoc[Webhooks for Liveboard schedule events]. +* See also: xref:webhooks.adoc[Webhooks] and xref:webhooks-lb-schedule.adoc[Webhooks for Liveboard schedule events]. * link:https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies-cross-account-resource-access.html[AWS IAM documentation] and link:https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_iam-s3.html[IAM and Amazon S3 troubleshooting guide]. diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index 0ec31acdb..5c06fd7ff 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -11,42 +11,42 @@ This page lists new features, enhancements, and deprecated functionality in Thou == Version 26.3.0.cl === ThoughtSpot integration with Amazon S3 storage for webhook delivery -You can now configure ThoughtSpot to deliver webhook payload and attachments directly into your own Amazon S3 storage using secure cross‑account access. To enable this integration, your AWS administrator must create an IAM Role and S3 permissions policy, and then register a webhook in ThoughtSpot to deliver the webhook payload and attachments directly to your S3 bucket. +You can now configure ThoughtSpot to deliver webhook payloads and attachments directly into your own Amazon S3 storage using secure AWS cross-account access. To enable this integration, your AWS administrator must create an IAM role with S3 permissions and trust policy, and then register a webhook in ThoughtSpot to deliver the payloads and attachments directly to your S3 bucket. For more information, see xref:webhooks-s3-storage.adoc[Amazon S3 storage integration for Webhook delivery]. === Host event enhancements for context-aware routing -HostEvents in the Visual Embed SDK are enhanced to improve event routing and context targeting in ThoughtSpot embedded applications. +HostEvents in the Visual Embed SDK are enhanced to improve event routing and context targeting in ThoughtSpot embedded applications. Developers can use the page context framework in the SDK to route host events to a specific UI layer and align user experience with the product UI behavior in multi-modal contexts. -For more information, see xref:events-context-aware-routing.adoc[ostEvents in multi‑modal contexts]. +For more information, see xref:events-context-aware-routing.adoc[HostEvents in multi-modal contexts]. === JWT-based ABAC implementation The legacy JWT-based approach that uses `filter_rules` and `parameter_values` to implement Attribute-Based Access Control (ABAC) is deprecated. As part of this deprecation, the following changes have been introduced to the custom authentication token API workflow and REST API Playground: -* The `filter_rules` parameter in the custom token authentication page in the REST API Playground is no longer be available for new configuration. This change doesn't affect your existing implementation. +* The `filter_rules` parameter on the custom token authentication page in the REST API Playground is no longer available for new configurations. This change does not affect your existing implementation. -* The `parameter_values` property is not deprecated in the 26.3.0.cl release version and remains supported until further notice. However, using parameter values for row level security use cases will be ultimately be deprecated in an upcoming release. +* The `parameter_values` property is not deprecated in version 26.3.0.cl and remains supported until further notice. However, using parameter values for row-level security use cases will ultimately be deprecated in an upcoming release. Existing ABAC implementations that use `filter_rules` will continue to function until further notice. However, we strongly recommend migrating your legacy ABAC implementation to the ABAC via RLS method that uses custom variables. For migration steps, refer to the ABAC migration guide. For new deployments, use ABAC via RLS with custom variables and pass data security attributes through the `variable_values` property in the custom access token, and define your RLS rules based on those variables. For more information, see xref:abac_rls-variables.adoc[ABAC via RLS]. === Spotter coaching access across published Orgs -Starting with 26.3.0.cl release, ThoughtSpot supports publishing Spotter coaching information to other Orgs. Coaching changes from the primary Org are synchronized with the data models published in secondary Orgs. +Starting with the 26.3.0.cl release, ThoughtSpot supports publishing Spotter coaching information to other Orgs. Coaching changes from the primary Org are synchronized with the data models published in secondary Orgs. -Administrators and users with edit access to data models can programmatically control user access to Spotter coaching information using the object privilege REST API endpoint, `/api/rest/2.0/security/metadata/manage-object-privilege`. They can assign `SPOTTER_COACHING_PRIVILEGE` to other users and user groups, allowing access to the coaching information without requiring them to have data model editing or administration privileges. +Administrators and users with edit access to data models can programmatically control user access to Spotter coaching information using the object privilege REST API endpoint, `/api/rest/2.0/security/metadata/manage-object-privilege`. They can assign `SPOTTER_COACHING_PRIVILEGE` to other users and user groups, allowing access to the coaching information without requiring data model editing or administration privileges. Users and groups with `SPOTTER_COACHING_PRIVILEGE` can import and export coaching TML on data models in the source and destination Orgs where the model is published, and can also share these objects with other users and groups. For more information, see xref:spotter-apis.adoc#_spotter_coaching_access[Spotter coaching access]. === Spotter embed enhancements -Developers can customize the appearance of the chat history sidebar in the embedded Spotter 3 interface. +Developers can customize the appearance of the chat history sidebar in the embedded Spotter 3 interface. For more information, see xref:embed-spotter.adoc#_chat_history_panel[Spotter embed documentation]. @@ -65,7 +65,7 @@ For information about REST API v2 enhancements, see the xref:rest-apiv2-changelo === SpotterCode extension for IDEs [earlyAccess eaBackground]#Early Access# -ThoughtSpot introduces SpotterCode, an AI-powered Model Context Protocol (MCP) extension for Integrated Development Environments (IDEs) such as Cursor, Visual Studio Code, and Claude Code. When integrated, SpotterCode enables the AI agent in the IDE to access ThoughtSpot SDKs and API documentation resources, and provide in-context coding assistance to developers embedding ThoughtSpot content within their applications. +ThoughtSpot introduces SpotterCode, an AI-powered Model Context Protocol (MCP) extension for Integrated Development Environments (IDEs) such as Cursor, Visual Studio Code, and Claude Code. When integrated, SpotterCode enables the AI agent in the IDE to access ThoughtSpot SDKs and API documentation resources and provide in-context coding assistance to developers embedding ThoughtSpot content within their applications. SpotterCode is available as an Early Access feature and can be integrated with development environments that support MCP servers and tools. For more information, see xref:spottercode.adoc[SpotterCode], xref:spottercode-integration.adoc[Integrating SpotterCode in IDEs], and xref:spottercode-prompt-guide.adoc[SpotterCode prompting guide]. @@ -81,7 +81,7 @@ To prevent excessive requests from reaching application servers and ensure API s For more information, see xref:about-rest-apis.adoc#_rate_limits_for_api_requests[Rate limits for REST APIs]. === Security settings via REST APIs -The security settings for ensuring data security and a seamless embedded user experience can now be done through REST APIs v2. Administrators and developers can configure allowlists for: +Security settings that ensure data security and a seamless embedded user experience can now be configured through REST APIs v2. Administrators and developers can configure allowlists for: * Content Security Policy (CSP) * Cross-origin Resource Sharing (CORS) @@ -101,14 +101,14 @@ For information about REST API v2 enhancements, see the xref:rest-apiv2-changelo === Theme Builder Theme Builder is now generally available (GA) and will be rolled out to all ThoughtSpot instances in customer deployments over the next few weeks. -When this feature is enabled on your instance, you can access it from the *Develop* page on your ThoughtSpot and use it to customize styles and UX themes directly within the product. +When this feature is enabled on your instance, you can access it from the *Develop* page in ThoughtSpot and use it to customize styles and UX themes directly within the product. For more information, see xref:theme-builder.adoc[Theme Builder]. === V3 navigation and home page experience The new V3 navigation and home page experience is now generally available (GA) and can be enabled on ThoughtSpot embedded instances. -The default UI experience in full application embedding remains classic (V1) experience until further notice. Developers embedding the full ThoughtSpot application can enable the V3 experience in their applications by setting the appropriate configuration options in their embed code. +The default UI experience in full application embedding remains the classic (V1) experience until further notice. Developers embedding the full ThoughtSpot application can enable the V3 experience in their applications by setting the appropriate configuration options in their embed code. For more information, see xref:full-app-customize.adoc[Customizing full application embedding]. @@ -121,9 +121,9 @@ For more information, see xref:abac_rls-variables.adoc[ABAC via RLS with variabl === Spotter APIs ThoughtSpot introduces new REST APIs for the following Spotter workflows: //* To get data source suggestions based on a user's query -* To send queries to a conversation session with Spotter agent -* To set natural language (NL) instructions on a Model to coach the Spotter system -* To fetch NL instructions configured on a Model +* To send queries to a conversation session with the Spotter agent +* To set natural language (NL) instructions on a model to coach the Spotter system +* To fetch NL instructions configured on a model For more information, see xref:spotter-apis.adoc[Spotter APIs]. From b1ebe651b3d4129c95354efb22513d5de6f7744b Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Fri, 27 Feb 2026 15:55:45 +0530 Subject: [PATCH 16/44] added changelog for lb sdk flags --- modules/ROOT/pages/api-changelog.adoc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index cd0e0e15e..3e38dd89c 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -8,14 +8,19 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For information about new features and enhancements available for embedded analytics, see xref:whats-new.adoc[What's New]. -== March 2026 +== Version 1.46.0, March 2026 [width="100%" cols="1,4"] |==== -|[tag greenBackground]#NEW FEATURE# | **`enableLinkOverridesV2`** + +|[tag greenBackground]#NEW FEATURE# | `enableLinkOverridesV2` + Use this enhanced configuration to override ThoughtSpot URLs on hover or when opening in a new tab. This is recommended over the earlier `linkOverride` flag for a better user experience. +|[tag greenBackground]#NEW FEATURE# a| **Liveboard experience enhancements** + +* The `isLiveboardXLSXCSVDownloadEnabled` attribute adds XLSX and CSV to the available Liveboard download formats. +* The `isGranularXLSXCSVSchedulesEnabled` attribute allows you to include the entire Liveboard, specific visualizations, or only tables and pivot tables in the XLSX and CSV schedules. + |==== == Version 1.45.0, February 2026 From 8741fa5122018a0181c66e4045cdc015431c2ca7 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 07:59:37 +0530 Subject: [PATCH 17/44] SCAL-280604 and SCAL-265929 --- modules/ROOT/pages/webhooks-lb-schedule.adoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/ROOT/pages/webhooks-lb-schedule.adoc b/modules/ROOT/pages/webhooks-lb-schedule.adoc index 0cba8e56b..0916d229d 100644 --- a/modules/ROOT/pages/webhooks-lb-schedule.adoc +++ b/modules/ROOT/pages/webhooks-lb-schedule.adoc @@ -1001,6 +1001,15 @@ Along with the JSON payload, if the Liveboard schedule is configured to send a P The payload also includes file attachments in the file format specified in the Liveboard schedule. The file format can be PDF, CSV, or XLSX. +=== Response after webhook delivery +The webhook must return an HTTP 2xx status code within approximately 5 seconds to indicate successful receipt and processing. Your server should therefore respond with a 2xx status within this timeframe after receiving the webhook delivery. All 2xx responses are recorded as `SUCCESS`. + +If your server takes longer than that to respond, returns a 4xx error or times out, ThoughtSpot will still deliver the Liveboard data and file, but the notification status is recorded as `FAILED` in the ThoughtSpot notification history and validation UI. + +If your server takes longer than 5 seconds to respond, returns a 4xx error, or times out, ThoughtSpot will still deliver the Liveboard data and file. However, the notification status is recorded as `FAILED` in the ThoughtSpot notification history. + +To ensure a timely response, we recommend processing webhook payloads asynchronously. Your server can immediately return a 2xx response upon receipt of the webhook and then handle the payload in the background without blocking subsequent webhook deliveries. + == Additional resources * link:https://docs.thoughtspot.com/cloud/latest/liveboard-schedule[Scheduling Liveboard jobs, window=_blank] From 0c8fa43e7e590160cc82a55419ded98bfacf85df Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 08:38:37 +0530 Subject: [PATCH 18/44] csp info removal and other fixes --- modules/ROOT/pages/mcp-integration.adoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index 6b877e5ae..35238d93c 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -67,8 +67,11 @@ ThoughtSpot administrators can use the SSO framework with SAML or OAuth token-ba * SAML redirect settings: + For SAML SSO users, the SAML redirect domain configuration is required to ensure that users are redirected to an allowed and trusted domain after they are authenticated. + * To get answers to their data queries, your application users require at least view access to ThoughtSpot data sources. To generate an Answer or to create a Liveboard, users require the data download privilege. + +//// * CSP and CORS settings: + To secure communication between the MCP client and the ThoughtSpot instance, administrators must add the MCP Server URL to CSP (Content Security Policy) and CORS (Cross-Origin Resource Sharing) allowlists in ThoughtSpot. +//// * Client connection configuration: + MCP Server integration also requires configuration on the client side, typically via a config file, to include the MCP Server addresses, credentials, and other details. @@ -104,13 +107,14 @@ Before you begin, verify if your application setup has the following: * Your application users have at least view access to the data source objects to query data and get answers. * Row-level and column-level security rules are configured for data security and access control. +//// To enable secure communication between the MCP Server and your ThoughtSpot instance, configure the following settings: . On your ThoughtSpot instance, navigate to *Develop* > *Customizations* > *Security Settings*. . Add the MCP Server domain to CSP and CORS allowlists. . If your setup uses SAML SSO logins, add the MCP Server domain to the SAML redirect domain allowlist. -//// + === Configure security settings on ThoughtSpot To allow secure communication between the MCP Server and your ThoughtSpot instance, configure the following settings: @@ -132,7 +136,7 @@ For OpenAI ChatGPT Deep Research, use the following URL: https://agent.thoughtspot.app/openai/mcp ---- -For MCP clients such as Claude Desktop, Windsurf, and Cursor that do not support a remote MCP Server, you must xref:mcp-integration.adoc#_connecting_other_mcp_clients_claude_desktop[add the MCP server configuration to your MCP client settings]. +For MCP clients that do not support a remote MCP Server, you must xref:mcp-integration.adoc#_connecting_other_mcp_clients_claude_desktop[add the MCP server configuration to your MCP client settings]. === Call MCP tools via LLM APIs From 9057247c3aa13c0ae8698714abf41215895df1f3 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 09:35:18 +0530 Subject: [PATCH 19/44] 00review edits --- modules/ROOT/pages/webhooks-lb-schedule.adoc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/ROOT/pages/webhooks-lb-schedule.adoc b/modules/ROOT/pages/webhooks-lb-schedule.adoc index 0916d229d..846fdec04 100644 --- a/modules/ROOT/pages/webhooks-lb-schedule.adoc +++ b/modules/ROOT/pages/webhooks-lb-schedule.adoc @@ -1002,11 +1002,9 @@ Along with the JSON payload, if the Liveboard schedule is configured to send a P The payload also includes file attachments in the file format specified in the Liveboard schedule. The file format can be PDF, CSV, or XLSX. === Response after webhook delivery -The webhook must return an HTTP 2xx status code within approximately 5 seconds to indicate successful receipt and processing. Your server should therefore respond with a 2xx status within this timeframe after receiving the webhook delivery. All 2xx responses are recorded as `SUCCESS`. +The webhook endpoint must respond with an HTTP 2xx status code to confirm successful receipt and processing of the request. The receiving server must send a 2xx response within 5 seconds of the webhook delivery. -If your server takes longer than that to respond, returns a 4xx error or times out, ThoughtSpot will still deliver the Liveboard data and file, but the notification status is recorded as `FAILED` in the ThoughtSpot notification history and validation UI. - -If your server takes longer than 5 seconds to respond, returns a 4xx error, or times out, ThoughtSpot will still deliver the Liveboard data and file. However, the notification status is recorded as `FAILED` in the ThoughtSpot notification history. +If your server takes longer than 5 seconds to respond, returns a 4xx error, or times out, ThoughtSpot may still deliver the Liveboard data and file. However, the notification status will be recorded as `FAILED` in the ThoughtSpot notification history and request will be retried. To ensure a timely response, we recommend processing webhook payloads asynchronously. Your server can immediately return a 2xx response upon receipt of the webhook and then handle the payload in the background without blocking subsequent webhook deliveries. @@ -1014,7 +1012,3 @@ To ensure a timely response, we recommend processing webhook payloads asynchrono * link:https://docs.thoughtspot.com/cloud/latest/liveboard-schedule[Scheduling Liveboard jobs, window=_blank] * +++Liveboard schedule REST APIs+++ - - - - From 7f935b1385a9860820e6fefd1e990ce67f7ac9df Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 10:54:17 +0530 Subject: [PATCH 20/44] SCAL-284461 updates --- modules/ROOT/pages/3rd-party-script.adoc | 7 ++++++- modules/ROOT/pages/security-settings.adoc | 7 +++++-- modules/ROOT/pages/whats-new.adoc | 7 +++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/3rd-party-script.adoc b/modules/ROOT/pages/3rd-party-script.adoc index 680cbfa75..a978ada50 100644 --- a/modules/ROOT/pages/3rd-party-script.adoc +++ b/modules/ROOT/pages/3rd-party-script.adoc @@ -1,4 +1,4 @@ -= Integrate external tools and allow custom scripts += External tools and script integration :toc: true :toclevels: 2 @@ -52,6 +52,11 @@ image::./images/csp-script-domain.png[CSS script-src domain] * The *CSP script-src domains* cannot be enabled and configured at the Org level. When configured, this setting will apply to all the Orgs configured on your instance. ==== +=== Allow Websocket endpoints +If your tool uses WebSockets, add the tool’s `wss://` endpoint to the CSP and CORS allowlists in ThoughtSpot. This enables secure WebSocket connections from an embedded ThoughtSpot page to the tool's WebSocket endpoint without being blocked by the browser’s Content Security Policy. + +Only hosts explicitly listed with `wss://` are permitted. You can add `wss://` URL in the **Develop** > **Security Settings** page. + == Passing variables to the hosted script To pass variables to the customer's hosted script, Visual Embed SDK provides the `customVariablesForThirdPartyTools` parameter. The `customVariablesForThirdPartyTools` is an object containing the variables that you wish to pass to the customer’s hosted JavaScript. These may include private information such as credentials or keys. The hosted JavaScript will access these variables via the `window.tsEmbed` object. diff --git a/modules/ROOT/pages/security-settings.adoc b/modules/ROOT/pages/security-settings.adoc index 24ecca882..80a485d06 100644 --- a/modules/ROOT/pages/security-settings.adoc +++ b/modules/ROOT/pages/security-settings.adoc @@ -137,7 +137,6 @@ curl -X POST 'https://{ThoughtSpot-Host}/api/rest/2.0/system/security-settings/c }' ---- - [#csp-connect-src] ==== Add URLs to CSP connect-src allowlist If you plan to use a custom action or webhook to send data to an external endpoint or application, you must add the domains of the target endpoints or applications to the `CSP connect-src` allowlist. @@ -329,7 +328,8 @@ curl -X POST \ ** CORS hosts — The UI allows adding a domain URL with the protocol (`http/https`). If the domain URLs are using `https`, you can exclude the protocol in domain URL strings, because ThoughtSpot assigns `https` to the URLs by default. ** For localhost and non-HTTPS URLs — For non-HTTPs domains or localhost such as `localhost:3000`, if you add the domain without the protocol, the `https` protocol will be assigned to the URL by default. Due to this, the localhost domain with `http` (`\http://localhost:3000`) might result in a CSP or CORS error. Therefore, include the `http` protocol in the domain name strings for non-HTTPS domains and localhost. * **Port**: If your domain URL has a non-standard port such as 8080, specify the port number in the domain name string. - +* **Websocket endpoints**: + +You can add Websocket (`wss://`) endpoints for external tool script integrations, for example, tools that open WebSocket connections from the browser. Only hosts explicitly listed with `wss://` are permitted. ==== The following table shows the valid domain name strings for the CORS and CSP allowlists. @@ -413,6 +413,9 @@ a| Domain names with space, backslash (\), and wildcard (*). a|+++Wildcard (*) for port+++ `thoughtspot:*`|[tag greenBackground tick]#✓# Supported |[tag greenBackground tick]#✓# Supported 2*|[tag greenBackground tick]#✓# Supported +a|Websocket URLs + +`wss://`| [tag greenBackground tick]#✓# Supported |[tag greenBackground tick]#✓# Supported |[tag redBackground tick]#x# Not Supported| [tag greenBackground tick]#✓# Supported +| |==== diff --git a/modules/ROOT/pages/whats-new.adoc b/modules/ROOT/pages/whats-new.adoc index 5c06fd7ff..a40155bfe 100644 --- a/modules/ROOT/pages/whats-new.adoc +++ b/modules/ROOT/pages/whats-new.adoc @@ -90,6 +90,13 @@ Security settings that ensure data security and a seamless embedded user experie For more information, see xref:security-settings.adoc[Security Settings]. +=== WebSocket support for external tools +ThoughtSpot supports secure WebSocket (`wss://`) endpoints for external tool script integrations, for example, tools that open WebSocket connections from the browser. + +To allow a WebSocket host, add the corresponding `wss://` URL to both your CSP allowlists. Only hosts explicitly listed with the `wss://` protocol are permitted. Existing `https://` entries in the allowlists remain unchanged and continue to function as expected. + +For more information, see xref:3rd-party-script.adoc#_allow_websocket_endpoints[External tools and script integration]. + === Visual Embed SDK For information about the new features and enhancements introduced in Visual Embed SDK version 1.45.0, see the xref:api-changelog.adoc[Visual Embed changelog]. From ef9a0b00646bebb3314b12b622c3fc5d9e2b19fc Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 21:02:10 +0530 Subject: [PATCH 21/44] SCAL-294970 fixes --- modules/ROOT/pages/common/nav.adoc | 1 + modules/ROOT/pages/mcp-integration.adoc | 489 +++-------- .../pages/mcp-server-client-connection.adoc | 814 ++++++++++++++++++ .../images/agents-mcp-server-arch.png | Bin 0 -> 188466 bytes 4 files changed, 943 insertions(+), 361 deletions(-) create mode 100644 modules/ROOT/pages/mcp-server-client-connection.adoc create mode 100644 static/doc-images/images/agents-mcp-server-arch.png diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index cdf26c325..eedcec0e2 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -222,6 +222,7 @@ include::generated/typedoc/CustomSideNav.adoc[] *** link:{{navprefix}}/integrate-SpotterCode[Integrating SpotterCode] *** link:{{navprefix}}/spottercode-prompting-guide[SpotterCode prompting guide] ** link:{{navprefix}}/mcp-integration[ThoughtSpot MCP server] +*** link:{{navprefix}}/connect-mcp-server-to-clients[Connect clients to ThoughtSpot MCP Server] * link:{{navprefix}}/development-and-deployment[Deployment and integration] ** link:{{navprefix}}/development-and-deployment[Development and deployment] diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index 35238d93c..7246df54a 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -1,414 +1,181 @@ -= MCP server integration += ThoughtSpot MCP Server :toc: true :toclevels: 3 :page-title: MCP integration :page-pageid: mcp-integration -:page-description: Learn how to use the ThoughtSpot Model Context Protocol (MCP) server to interact with ThoughtSpot data via MCP tools and AI APIs and get relevant questions and answers for a given query and create Liveboards at runtime. +:page-description: Learn how to use the ThoughtSpot Model Context Protocol (MCP) server to interact with ThoughtSpot data via MCP tools -ThoughtSpot’s Agentic Model Context Protocol (MCP) Server allows you to integrate ThoughtSpot analytics directly into any AI agent, custom chatbot, or LLM-based platforms that support MCP. It acts as a connector between the ThoughtSpot instance and external AI client, and provides a set of tools for interacting with ThoughtSpot’s data and its analytics capabilities programmatically. - +ThoughtSpot’s Agentic Model Context Protocol (MCP) Server allows you to integrate ThoughtSpot analytics into any AI agent, custom chatbot, or LLM platform that supports MCP. + +Instead of rebuilding analytics logic yourself, you connect an LLM/AI agent to the ThoughtSpot MCP Server. The LLM can then: + +* Discover ThoughtSpot MCP tools automatically +* Ask questions in natural language +* Create Liveboards programmatically from answer sessions + +== Get access to MCP Server The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. + To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: * Enterprise Edition of ThoughtSpot Analytics -* ThoughtSpot Embedded subscription +* ThoughtSpot Embedded -To learn more about the MCP Server subscription options and to get started, please contact your ThoughtSpot Sales representative. +To learn more about subscription options, contact your ThoughtSpot Sales representative. -== Integration overview +== Spotter and MCP Server -The Agentic MCP Server integration requires the following core components and authentication framework: +ThoughtSpot provides three main options to integrate AI and analytics: -MCP Server:: -The MCP Server exposes a set of tools that can be invoked by an LLM or external AI. ThoughtSpot's MCP Server acts as a bridge between the LLM/agent and ThoughtSpot application backend. +[cols="1,3",options="header"] +|=== +| Feature | Purpose -MCP tools and resources:: -MCP tools are the actions that the MCP Server exposes to the agent for interaction with ThoughtSpot. +|**Spotter Embed** +| Allows embedding Spotter conversational UI and agentic capabilities directly into your application using the Visual Embed SDK. +Requires minimal development effort and supports faster deployment. This option is recommended for conversational analytics experiences within your application context. -* Ask natural language questions and get data in a structured format from ThoughtSpot -* Retrieve relevant analytical questions based on user queries -* Create a Liveboard with the answers generated from the queries -//* Get data source recommendations based on a user's query and intent +| **ThoughtSpot MCP Server** +a| Allows using your own UI, agent or LLM, and orchestration. ThoughtSpot exposes governed analytics tools via the MCP protocol, allowing your agent to discover and call tools programmatically. This method is recommended for using ThoughtSpot as a plug-in analytics engine in your AI experience. -+ -Currently, the MCP Server supports the following tools: +Use ThoughtSpot MCP Server when: -* `ping` to test connection to ThoughtSpot -* `getRelevantQuestions` to get relevant analytical questions + -The `getRelevantQuestions` tool to fetch relevant data questions for a given data context by breaking down a user's query. -* `getAnswer` to execute the queries and fetch data + -The `getAnswer` tool generates answers and insights for a given data context. -* `createLiveboard` to create a Liveboard in ThoughtSpot + +* Integrating with agents that natively support MCP, such as Claude, OpenAI, Gemini, or custom MCP clients. -The `createLiveboard` tool calls the Liveboard creation workflow and creates a Liveboard with the answers generated from the user's query. +* You want to reuse ThoughtSpot’s governed analytics and data security controls such as Row-level Security (RLS), Column-level Security (CLS) rules, Liveboards, and data modeling options, instead of building analytics logic in your application. -//// -* `getDataSourceSuggestions` to get data source suggestions + -Based on the type of data that users want to fetch, `getDataSourceSuggestions` gets a list of data source recommendations. Currently, `getDataSourceSuggestions` is not exposed as an MCP tool and is available as an MCP `resource`. To get data source suggestions, the user or MCP client must have at least view access to ThoughtSpot data sources. -//// +* You want the LLM to discover and call tools via the MCP protocol, rather than connecting every endpoint manually. -MCP client/ LLM agent:: -The external system or application environment with AI Agent, Claude, OpenAI, or a custom chatbot that acts as a user interface and orchestrates interaction with the ThoughtSpot MCP Server. -This is the model or system that processes the user’s natural language input, determines which tool to call, and integrates the tool results into its final output. +| Spotter APIs +| Provides REST APIs for full programmatic control over analytics workflows. You can use your own LLM and orchestration logic to interact with Spotter and retrieve structured answers, charts, or relevant questions for a specific data model. +|=== -//// -Configuration settings to enable the integration:: -Integration requires configuration, typically via a config file, to specify server addresses, credentials, and other connection details. -//// - -Authentication and security settings:: - -* Access to ThoughtSpot instance + -For MCP Server connection, users require access to a ThoughtSpot instance. For tool invocation, the MCP server must accept authenticated requests, and the LLM tool specification must carry those credentials or headers. + -ThoughtSpot administrators can use the SSO framework with SAML or OAuth token-based authentication methods to authenticate and sign in users. + +== Architecture and roles -* SAML redirect settings: + -For SAML SSO users, the SAML redirect domain configuration is required to ensure that users are redirected to an allowed and trusted domain after they are authenticated. + -* To get answers to their data queries, your application users require at least view access to ThoughtSpot data sources. To generate an Answer or to create a Liveboard, users require the data download privilege. - -//// -* CSP and CORS settings: + -To secure communication between the MCP client and the ThoughtSpot instance, administrators must add the MCP Server URL to CSP (Content Security Policy) and CORS (Cross-Origin Resource Sharing) allowlists in ThoughtSpot. -//// -* Client connection configuration: + -MCP Server integration also requires configuration on the client side, typically via a config file, to include the MCP Server addresses, credentials, and other details. +A typical implementation with ThoughtSpot MCP Server includes the following core components: +[width="100%" cols="2,4"] +[options='header'] +|====== +|Component|Role +|*Agent or LLM* +a| Acts as orchestrator +- Receives the user’s prompt. +- Discovers ThoughtSpot MCP tools via the MCP protocol. +- Decides which tools to call and in what order. +- Combines ThoughtSpot results with other sources and generates the final answer. +|*ThoughtSpot MCP Server* a| +- Acts as a gateway between the agent and ThoughtSpot. +- Exposes analytics as MCP tools, such as `ping`, `getDataSourceSuggestions`, `getRelevantQuestions`, `getAnswer`, and `createLiveboard`. +- Enforces data security with RLS and CLS rules. +- Wraps AI REST APIs. +|*Client Interface* a| +The user-facing interface that renders chat, responses, and charts. For example, Claude AI web app, Claude Desktop, ChatGPT or OpenAI integrations, Gemini-based agents, custom web applications, or internal tools. +|====== -=== How it works - -The MCP Server integration with an agentic framework or LLM clients enables the following workflow: - -. User sends a query to get data from a specific ThoughtSpot data model context. -. The LLM / AI agent receives the request and sends it to the MCP server endpoint with the user's query. -. The MCP server responds with the available tools. - -. The LLM / AI Agent determines the appropriate MCP tool to call. Based on the user's query or prompt, the MCP tools are invoked. For example, to get information for a specific data context from ThoughtSpot, break down the user's query into relevant questions or programmatically create an artifact in ThoughtSpot. -. The MCP server processes the request and returns the result. -. The agent receives the response, constructs the output, and presents it to the user. -. User receives the response. The user can refine the analysis with follow-up queries for further exploration or ask a new question. + -For example, after receiving relevant questions and answers, the user can send follow-up questions or initiate a Liveboard creation request. - -The following figure illustrates the sequence of workflows in a typical MCP Server integration setup: +The interaction between the user, agent, and MCP Server is illustrated in the following figure: [.widthAuto] -image::./images/mcp-integration.png[MCP integration] - -== Get started -To get started with the integration, complete the steps described in the following sections. In this article, we'll integrate ThoughtSpot MCP Server with Claude and enable agentic interaction and workflows. - -=== Before you begin +image::./images/agents-mcp-server-arch.png[MCP integration] -Before you begin, verify if your application setup has the following: +== How it works +The MCP Server implementation with an agentic framework or LLM client typically involves the following workflow: -* Node.js version 22 or later is installed. -* A ThoughtSpot instance with 10.11.0.cl or later release version. You'll need administrator credentials to configure security settings or set up token-based authentication for your application users. -* Your application users have at least view access to the data source objects to query data and get answers. -* Row-level and column-level security rules are configured for data security and access control. - -//// -To enable secure communication between the MCP Server and your ThoughtSpot instance, configure the following settings: - -. On your ThoughtSpot instance, navigate to *Develop* > *Customizations* > *Security Settings*. -. Add the MCP Server domain to CSP and CORS allowlists. -. If your setup uses SAML SSO logins, add the MCP Server domain to the SAML redirect domain allowlist. - - -=== Configure security settings on ThoughtSpot +. *User asks a question* + +User sends a query in the chat interface to get data. For example, `What were the total sales of Jackets and Bags in the Northeast last year?` + ++ +Optionally, the user can specify the data context so that data from a specific source is used to generate answers. -To allow secure communication between the MCP Server and your ThoughtSpot instance, configure the following settings: +. *(Optional) Data source selection via `getDataSourceSuggestions`* + +If the question doesn’t specify a data source, the agent can call `getDataSourceSuggestions`. ++ +ThoughtSpot returns multiple candidate data sources (models) with confidence scores and reasoning. -. On your ThoughtSpot instance, navigate to *Develop* > *Customizations* > *Security Settings*. -. Add the MCP Server domain to CSP and CORS allowlists. -. If your setup uses SAML SSO logins, add the MCP Server domain to the SAML redirect domain allowlist. -//// +. **Query decomposition** + +The user's query is decomposed into smaller questions via `getRelevantQuestions`. ++ +The agent calls `getRelevantQuestions` with the following parameters: + +* The user query (`query`) +* One or more `datasourceIds` ++ +ThoughtSpot returns the AI-suggested, schema-aware questions that are easier to run analytically. -=== Connect your client to the MCP Server +. *Answer generation via `getAnswer`* + +For each suggested or chosen question, the agent calls `getAnswer` with: ++ +* `question` +* `datasourceId` ++ +ThoughtSpot returns the following: ++ +* Preview data (CSV string) for LLM reasoning. +* Visualization metadata, including an embeddable `frame_url`. +* `session_identifier` and `generation_number` for charts that are used for creating Liveboards. -If using a client that supports remote MCPs natively, such as Claude AI, use the following MCP server URL: ----- -https://agent.thoughtspot.app/mcp ----- +. *(Optional) Liveboard creation via `createLiveboard`* + +To save one or more answers as a Liveboard: ++ +* The agent extracts `question`, `session_identifier`, and `generation_number` from each `getAnswer` response. +* Calls `createLiveboard` with: +** `name` – Name of the Liveboard. +** `noteTile` – descriptive HTML text shown as a note tile. +** `answers` – array of answers from `getAnswer`. ++ +ThoughtSpot creates a Liveboard and returns identifiers and a `frame_url` for the Liveboard. -For OpenAI ChatGPT Deep Research, use the following URL: ----- -https://agent.thoughtspot.app/openai/mcp ----- +. *User experience for chat sessions* ++ +In MCP platforms such as Claude and ChatGPT, users typically see a natural-language summary with a link to a ThoughtSpot Liveboard. ++ +In custom apps, you can: -For MCP clients that do not support a remote MCP Server, you must xref:mcp-integration.adoc#_connecting_other_mcp_clients_claude_desktop[add the MCP server configuration to your MCP client settings]. +* Embed the `frame_url` in iframes to show interactive charts inline. +* Provide Call To Action (CTA) elements backed by `createLiveboard`. -=== Call MCP tools via LLM APIs +== Authentication for MCP Server -ThoughtSpot remote MCP Server acts as a wrapper over the ThoughtSpot APIs, making them available as tools for agent frameworks or LLMs such as Claude or OpenAI. It exposes specific tools that can be invoked by the LLMs in response to a user's query or prompt. +The MCP Server always runs under an authenticated ThoughtSpot user context. You can authenticate in two main ways: -To enable tool calling: +OAuth:: +Use OAuth when: +* Connecting plug-and-play MCP platforms such as Claude, Gemini, ChatGPT integrations, and more. +* You want the platform to drive a browser-based sign-in flow. -* Register the ThoughtSpot MCP Server endpoint as a tool provider in your LLM or agent framework. -* Provide an authentication (OAuth or token-based) token. + -You can generate an authentication token for a specific user from ThoughtSpot via a `POST` call to the `/api/rest/2.0/auth/token/full` REST API endpoint. + -Logged-in users can view the authentication token for their current session by using the `/api/rest/2.0/auth/session/token` REST API endpoint or by opening the following URL in a new tab on the web browser: + -`\https://{your-ts-instance}/api/rest/2.0/auth/session/token` - -For information about calling MCP tools using LLM APIs and methods, see these sections: - -* xref:mcp-integration.adoc#_claude_mcp_connector[Claude MCP connector] -* xref:mcp-integration.adoc#_openai_api_for_mcp_tool_calling[OpenAI API] -* xref:mcp-integration.adoc#_gemini_api[Gemini API and function calling] - -==== Claude MCP connector -The Claude’s MCP connector allows you to connect to remote MCP Servers directly from the Messages API. - -To connect to the ThoughtSpot remote MCP Server, specify the following properties in the API request: - -* `mcp_servers` + -In the `mcp_servers` array, include these parameters: + -** `type` + -__String__. Type. Specify the type as `url`. -** `url` + -__String__. The URL of the remote MCP Server endpoint. Must start with `https://`. -** `name` + -__String__. A unique identifier/label for the MCP Server. It will be used in the MCP tool call blocks to identify the server and to disambiguate tools to the LLM. -** `authorization_token` + -__String__. OAuth authorization token (`TS_AUTH_TOKEN`) along with the ThoughtSpot application instance URL. In the following example, the authorization token is added as a prefix, and the ThoughtSpot host URL is added with the `@` symbol. - -* `messages` + -In the `messages` array, specify a natural language question in `content` and the user role in `role`. - -* `model` + -LLM model to use for processing queries and interacting with tools. For example, claude-sonnet-4-20250514. - -[source,cURL] ----- -curl https://api.anthropic.com/v1/messages \ - -H "Content-Type: application/json" \ - -H "X-API-Key: $ANTHROPIC_API_KEY" \ - -H "anthropic-version: 2023-06-01" \ - -H "anthropic-beta: mcp-client-2025-04-04" \ - -d '{ - "model": "claude-sonnet-4-20250514", - "max_tokens": 1000, - "messages": [{ - "role": "user", - "content": "How do I increase my sales ?" - }], - "mcp_servers": [ - { - "type": "url", - "url": "https://agent.thoughtspot.app/bearer/mcp", - "name": "thoughtspot", - "authorization_token": "$TS_AUTH_TOKEN@my-thoughtspot-instance.thoughtspot.cloud" - } - ] - }' ----- +In a typical OAuth flow: -//// -[source,TypeScript] ----- -import { Anthropic } from '@anthropic-ai/sdk'; - -const anthropic = new Anthropic(); - -const response = await anthropic.beta.messages.create({ - model: "claude-sonnet-4-5", - max_tokens: 1000, - messages: [ - { - role: "user", - content: "How do I increase my sales ?", - }, - ], - mcp_servers: [ - { - type: "url", - url: "https://agent.thoughtspot.app/bearer/mcp", - name: "thoughtspot", - authorization_token: "$TS_AUTH_TOKEN@my-thoughtspot-instance.thoughtspot.cloud", - }, - ], - betas: ["mcp-client-2025-04-04"], -}); ----- -//// +. ThoughtSpot MCP Server is configured as an MCP tool/connection in the client. +. Client redirects the user to ThoughtSpot to sign in. +. Client stores the OAuth token and passes it to the MCP Server on each tool call. -The request uses Claude’s internal tool-calling mechanism to call the MCP endpoint with the provided token, discover the available tools, and retrieve data for the user's query. - -For more information, see the link:https://docs.claude.com/en/docs/agents-and-tools/mcp-connector[Claude MCP connector documentation, window=_blank]. - -==== OpenAI API for MCP tool calling -To enable tool calling and retrieve data from ThoughtSpot via OpenAI, you can use the Responses API endpoint. - -To connect to the ThoughtSpot remote MCP server, call the `\https://api.openai.com/v1/responses` API endpoint and specify the following properties in the API request: - -* `tools` + -In the `tools` array, include these parameters: - -** `server_url` + -The URL of the ThoughtSpot MCP Server. Use the full path of the MCP server URL. -** `server_label` + -Label of the ThoughtSpot MCP Server -** `type` + -Type of tool. For example, MCP. -** `headers` + -Additional headers needed for authentication, for example, the authentication token and URL of the ThoughtSpot host. - -* `input` + -Include the natural language query string as `input`. -* `model` + -LLM model to use for processing queries and interaction with tools. For example, GPT-5 or GPT 4.1. - -[source,cURL] ----- -curl https://api.openai.com/v1/responses \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "gpt-4.1", - "tools": [ - { - "type": "mcp", - "server_label": "thoughtspot", - "server_url": "https://agent.thoughtspot.app/bearer/mcp", - "headers": { - "Authorization": "Bearer $TS_AUTH_TOKEN", - "x-ts-host": "my-thoughtspot-instance.thoughtspot.cloud" - } - } - ], - "input": "How can I increase my sales ?" -}' ----- - -If the API request is successful, the LLM discovers the available MCP tools from the MCP Server endpoint. Once the model has access to these tools, it determines the tool to call depending on the user's query and what's in the model's context. - -For more information, see link:https://platform.openai.com/docs/guides/tools-connectors-mcp[Open AI Connectors and MCP Server Documentation]. - -==== Gemini API - -You can use the standard function calling mechanism provided in Gemini Python/Typescript SDK. The Gemini SDK supports MCP natively, and can pass tool definitions and call tools. - -In the following example, a session linked to the ThoughtSpot remote MCP Server is passed along with the authorization token and the ThoughtSpot host, so that the SDK can handle tool calling. - -[source,TypeScript] ----- -import { GoogleGenAI, FunctionCallingConfigMode , mcpToTool} from '@google/genai'; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; - -// Create server parameters for stdio connection -const serverParams = new StreamableHTTPClientTransport(new URL("https://agent.thoughtspot.app/bearer/mcp"), { - requestInit: { - headers: { - "Authorization": "Bearer $TS_AUTH_TOKEN", - "x-ts-host": "my-thoughtspot-instance.thoughtspot.cloud" - }, - } -}); - -const client = new Client( - { - name: "example-client", - version: "1.0.0" - } -); - -// Configure the client -const ai = new GoogleGenAI({}); - -// Initialize the connection between client and server -await client.connect(serverParams); - -// Send request to the model with MCP tools -const response = await ai.models.generateContent({ - model: "gemini-2.5-flash", - contents: `What is the weather in London in ${new Date().toLocaleDateString()}?`, - config: { - tools: [mcpToTool(client)], // uses the session, will automatically call the tool - // Uncomment if you **don't** want the sdk to automatically call the tool - // automaticFunctionCalling: { - // disable: true, - // }, - }, -}); -console.log(response.text) - -// Close the connection, -await client.close(); ----- - -For additional information, refer to the following resources: - -* For more information about Gemini API MCP tool calling, see link:https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#mcp[Function calling with the Gemini API documentation, window=_blank]. -* A link:https://github.com/thoughtspot/developer-examples/tree/main/mcp/python-google-adk-trusted-auth[developer example with Google ADK and Python implementation] is also available in the link:https://github.com/thoughtspot/developer-examples[ThoughtSpot Developer Examples GitHub repository, window=_blank]. -* The ThoughtSpot MCP server can also be installed as a Gemini CLI extension. For more information, see link:https://github.com/google-gemini/gemini-cli[Gemini CLI, window=_blank]. - -=== For clients that do not support the remote MCP server - -For clients such as Claude Desktop, Windsurf, and Cursor, which do not support remote MCP servers, add the following configuration to your MCP client settings: - -[source,JSON] ----- -{ - "mcpServers": { - "ThoughtSpot": { - "command": "npx", - "args": [ - "mcp-remote", - "https://agent.thoughtspot.app/mcp" - ] - } - } -} ----- - -After updating the config file: - -. When prompted to connect your ThoughtSpot instance, add the URL of your application instance and complete authentication. -. Restart your MCP client to load the new configuration. -+ -If the connection is successful, you'll see an option to connect to ThoughtSpot and choose the data context. -+ -For example, the Claude Desktop shows the *Add to ThoughtSpot* as shown in the following figure: -+ -[.bordered] -[.widthAuto] -image::./images/claudeDesktop.png[Claude Desktop] +Token-based trusted authentication:: +Use trusted authentication if: -. Verify if the MCP tools are available. + -For example, on Claude Desktop, click the Search and tools icon to view the MCP tools. -+ -[.bordered] -[.widthAuto] -image::./images/mcp-tools-claude.png[Claude Desktop] +* You are building a custom UI or custom MCP client. +* You handle user identity and want seamless SSO from your app into ThoughtSpot. -. Select a data source to set the context of your query and verify the request and response flow. + -[.bordered] -[.widthAuto] -image::./images/query-response-claude.png[Claude query response] +In a typical trusted authentication flow: -. Try sending a query to create a Liveboard and verify if a Liveboard is created on your ThoughtSpot instance. -+ -[.bordered] -[.widthAuto] -image::./images/create-lb-claude.png[Liveboard creation] +. The authenticator service at the backend obtains a ThoughtSpot token via API calls to the following ThoughtSpot REST API endpoints: +** `POST /api/rest/2.0/auth/token/full` +** `GET /api/rest/2.0/auth/session/token`. +. The token generated for the user session is then passed to the MCP Server as a bearer token header, or as part of the client's MCP server configuration. For example, `authorization_token` for Claude. -== Configuration considerations and best practices +//// +[NOTE] +==== +Trusted authentication tokens used only as HTTP headers do not create a browser session by themselves. For embedded sessions, you may also need a cookie. +==== +//// -* Users must have at least view access to the data source. Otherwise, it may lead to empty results. -* Ensure that data is modeled. Large or complex data sources may impact response time. -* Streaming responses require client support for real-time updates. Ensure that your system is available to receive and process data. -* Each conversation is session-based. Ensure that session IDs are managed correctly in your integration. +== Connecting MCP clients +For information about supported platforms, how to connect MCP clients, examples, and best practices, see xref:mcp-server-client-connection.adoc[Connect clients to ThoughtSpot MCP server]. == Additional resources -* Check the link:https://github.com/thoughtspot/mcp-server[MCP Server GitHub repo, window=_blank] for implementation instructions. -* Check your MCP client's documentation for instructions on how to connect to MCP Servers. -* In case of issues with connection or authentication, refer to the link:https://github.com/thoughtspot/mcp-server?tab=readme-ov-file#troubleshooting[troubleshooting steps^]. -* To understand ThoughtSpot's agentic analytics capabilities and AI APIs, refer to the following documentation: +* For information about MCP, see link:https://modelcontextprotocol.io[Model Context Protocol specification, window=_blank]. +* For information about connecting MCP clients, see xref:mcp-server-client-connection.adoc[Connecting MCP clients to ThoughtSpot MCP server]. +* For implementation details, see link:https://github.com/thoughtspot/mcp-server[MCP Server GitHub repository, window=_blank]. -** link:https://docs.thoughtspot.com/cloud/latest/spotter[Spotter Documentation, window=_blank] -** link:https://docs.thoughtspot.com/cloud/latest/spotter-agent[Spotter Agent Documentation, window=_blank] -** xref:spotter-apis.adoc[Spotter AI APIs] diff --git a/modules/ROOT/pages/mcp-server-client-connection.adoc b/modules/ROOT/pages/mcp-server-client-connection.adoc new file mode 100644 index 000000000..67be7823b --- /dev/null +++ b/modules/ROOT/pages/mcp-server-client-connection.adoc @@ -0,0 +1,814 @@ += Connect clients to ThoughtSpot MCP Server +:toc: true +:toclevels: 3 + +:page-title: MCP integration +:page-pageid: connect-mcp-server-to-clients +:page-description: Learn how to connect ThoughtSpot MCP server to clients and call tools + +To connect clients to the ThoughtSpot MCP server, add the MCP server endpoint to your LLM client. + +Authentication is handled per user, typically using OAuth. When connected, the client can discover available MCP tools, select data sources, and interact with ThoughtSpot analytics by calling MCP tools. The MCP server exposes only the data sources and functions the authenticated user is allowed to access, and all actions are performed in the context of that user’s security entitlements. + +== Before you begin +Before you begin, check the following prerequisites and ensure that the required configuration and setup are available for connecting your client to the ThoughtSpot MCP server. + +* Node.js version 22 or later is installed for node-based examples and local clients. +* A ThoughtSpot application instance with 10.11.0.cl or a later release version. +* Users have the necessary privileges to view data from relevant models/tables in ThoughtSpot. Existing RLS/CLS rules on tables are enforced automatically in data source responses. +* For Answer and Liveboard creation, the user must have the data download and content-creation privileges. + +== Connecting Remote MCP-aware clients +If you are using a client that supports remote MCPs natively, use the following MCP server endpoint: + +`https://agent.thoughtspot.app/mcp` + +For clients that require a bearer token for authentication: + +`https://agent.thoughtspot.app/bearer/mcp` + +For OpenAI MCP and Responses API integration, use the following URL: + +`https://agent.thoughtspot.app/openai/mcp` + +For additional information, refer to your client’s documentation for how to register a remote MCP server. + +Once registered, the agent discovers ThoughtSpot tools from the MCP endpoint and calls tools such as `getRelevantQuestions`, `getAnswer`, and `createLiveboard` as needed to answer the user's question. + +=== Claude MCP connector + +The following example shows the code to connect Claude to the MCP server: + +[source,bash] +---- +curl https://api.anthropic.com/v1/messages \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: mcp-client-2025-04-04" \ + -d '{ + "model": "claude-3-5-sonnet-latest", + "max_tokens": 1000, + "messages": [{ + "role": "user", + "content": "How do I increase my sales?" + }], + "mcp_servers": [ + { + "type": "url", + "url": "https://agent.thoughtspot.app/bearer/mcp", + "name": "thoughtspot", + "authorization_token": "TS_AUTH_TOKEN@my-instance.thoughtspot.cloud" + } + ] + }' +---- + + +=== OpenAI Responses API (MCP tools) + +The following example shows the code to connect OpenAI to the MCP server: + +[source,bash] +---- +curl https://api.openai.com/v1/responses \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "gpt-4.1", + "tools": [ + { + "type": "mcp", + "server_label": "thoughtspot", + "server_url": "https://agent.thoughtspot.app/bearer/mcp", + "headers": { + "Authorization": "Bearer TS_AUTH_TOKEN", + "x-ts-host": "my-instance.thoughtspot.cloud" + } + } + ], + "input": "How can I increase my sales?" + }' +---- + +=== Gemini with MCP + +The following example shows the code to connect Gemini to the MCP server: + +[source,typescript] +---- +import { + GoogleGenAI, + mcpToTool, +} from '@google/genai'; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; + +const transport = new StreamableHTTPClientTransport( + new URL("https://agent.thoughtspot.app/bearer/mcp"), + { + requestInit: { + headers: { + "Authorization": "Bearer TS_AUTH_TOKEN", + "x-ts-host": "my-instance.thoughtspot.cloud" + }, + } + } +); + +const mcpClient = new Client({ + name: "example-client", + version: "1.0.0", +}); + +await mcpClient.connect(transport); + +const ai = new GoogleGenAI({}); + +const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: `Show me last quarter's sales by region`, + config: { + tools: [mcpToTool(mcpClient)], + }, +}); + +console.log(response.text); +await mcpClient.close(); +---- + +== Connecting clients that do not natively support remote MCP servers + +Some clients that do not natively support configuring a remote MCP URL may require an `mcp-remote` component. In such cases, configure the MCP server as shown in this example: + +[source,JSON] +---- +{ + "mcpServers": { + "ThoughtSpot": { + "command": "npx", + "args": [ + "mcp-remote", + "https://agent.thoughtspot.app/mcp" + ] + } + } +} +---- + +== Internal API routes + +If you are building your own web app or chatbot, you might want to set up internal routes that act as messengers, passing requests from your app to the ThoughtSpot MCP Server or REST APIs and then returning the results to your app. This allows your app to fetch data or answers from ThoughtSpot without connecting to it directly. + +The sample patterns in the following examples define two internal routes for a Next.js app. You can adapt this pattern to other backend frameworks: + +* `/api/mcp` + +Forwards tool calls to the ThoughtSpot MCP server. +* `/api/search-worksheets` + +Searches models using the ThoughtSpot REST API. + +=== POST /api/mcp + +Serves as a proxy endpoint for calling ThoughtSpot MCP tools from the client side. This route forwards requests from your frontend to the ThoughtSpot MCP tools. + +==== Request parameters + +[cols="1,1,3",options="header"] +|=== +|Parameter +|Required? +|Description + +|`toolName` +|Yes +|Name of the MCP tool to call. For example, `getRelevantQuestions`, `getAnswer`, or `createLiveboard`. + +|`args` +|Yes +|Input object for the selected tool. + +|`tsHost` +|Yes +|ThoughtSpot instance URL. For example, `https://my-instance.thoughtspot.cloud`. + +|`authToken` +|Yes +|ThoughtSpot bearer token used for authentication. +|=== + +==== Example request +[source,TypeScript] +---- +const response = await fetch("/api/mcp", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + toolName: "getAnswer", + args: { + question: "Total sales by region", + datasourceId: "model-guid" + }, + tsHost: "https://my-instance.thoughtspot.cloud", + authToken: "your-bearer-token" + }) +}); +---- + +==== Example response + +[source,json] +---- +{ + "success": true, + "tool": "getAnswer", + "args": { "question": "...", "datasourceId": "..." }, + "result": { + "content": "JSON string with tool response", + "rawResult": { }, + "timestamp": "2024-01-01T00:00:00.000Z" + } +} +---- + +The `result.content` field is a JSON string. Parse it before use: + +[source,TypeScript] +---- +const parsed = JSON.parse(answerResult.result.content); +---- + +=== POST /api/search-worksheets + +This route searches for data sources such as models in ThoughtSpot using the REST API. + +==== Request parameters + +[cols="1,1,2",options="header"] +|=== +| Parameter | Required | Description +| `tsHost` | Yes | __String__. ThoughtSpot instance URL +| `authToken` | Yes |__String__. Bearer token for authentication +| `namePattern` |No |__String__. Pattern to filter model names. Default is `*`. +|=== + +==== Example request + +[source,TypeScript] +---- +const response = await fetch("/api/search-worksheets", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + tsHost: "https://my-instance.thoughtspot.cloud", + authToken: "your-bearer-token", + namePattern: "*" // optional, defaults to "*" + }) +}); +---- + +==== Example response + +[source,json] +---- +{ + "worksheets": [ + { + "id": "worksheet-guid", + "name": "Sales Data", + "description": "Sales metrics and dimensions", + "created": 1234567890, + "modified": 1234567890, + "author": "John Doe" + } + ] +} +---- + +Use the `id` value as `datasourceId` when calling MCP tools. + +== MCP tools and responses +All MCP tools are called via the MCP protocol. However, in many custom apps, you'll also call them through a proxy endpoint like `/api/mcp`, using a helper such as `callMCPTool(toolName, args)`. + +[source,TypeScript] +---- +async function callMCPTool(toolName: string, args: any) { + const response = await fetch("/api/mcp", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + toolName, + args, + tsHost: "https://my-instance.thoughtspot.cloud", + authToken: "your-bearer-token" + }) + }); + return response.json(); +} +---- + +The available tools are: +* `ping` +* `getDataSourceSuggestions` +* `getRelevantQuestions` +* `getAnswer` +* `createLiveboard` + + +=== ping +Runs a health check to validate that the MCP Server is reachable and authenticated. + +=== getDataSourceSuggestions + +Suggests appropriate ThoughtSpot data sources for a given question. + +==== Query attributes + +[source,TypeScript] +---- +{ + query: string; // User's natural language query +} +---- + +==== Response example + +Returns an array of suggestions, each including: + +* `header.guid` + +Data source GUID that will be used as `datasourceId` later in subsequent queries. +* `header.displayName` + +Display name +* `header.description` + +Description. +* `confidence` + +Numeric confidence score. +* `llmReasoning` + +Human-readable reasoning for the suggestion. + + +=== getRelevantQuestions + +Gets AI-suggested analytical questions relevant to the user’s query for a given data context. + +==== Query attributes + +[source,Typescript] +---- +{ + query: string; // User's natural language query + datasourceIds: string[]; // Array of worksheet/datasource GUIDs +} +---- + +==== Example call + +[source,Typescript] +---- +const result = await callMCPTool("getRelevantQuestions", { + query: "show me sales data", + datasourceIds: ["model-guid-123"] +}); +---- + +==== Parsed response example + +[source,json] +---- +{ + "questions": [ + "What is the total sales by region?", + "Which products have the highest revenue?", + "What are the top selling categories?" + ] +} +---- + +These questions can then be passed individually into `getAnswer` calls. + +=== getAnswer + +Generates a visualization from a natural language question for a given context. + +==== Query attributes + +[source,TypeScript] +---- +{ + question: string; // Natural language question + datasourceId: string; // Worksheet/datasource GUID + context?: string; // Optional conversation context +} +---- + +==== Example call + +[source,TypeScript] +---- +const result = await callMCPTool("getAnswer", { + question: "Total sales by region", + datasourceId: "worksheet-guid-123" +}); +---- + +==== Response example + +[source,JSON] +---- +{ + "question": "Total sales by region", + "session_identifier": "abc-123-def-456", + "generation_number": 2, + "data": "\"Region\",\"Total Sales\"\n\"East\",100000\n...", + "frame_url": "https://...", + "fields_info": "..." +} +---- + +===== Key fields +[cols="1,3",options="header"] +|=== +|Field +|Description + +|`session_identifier` +|Unique session ID used to group answers. Required when creating a Liveboard from this answer via `createLiveboard`. + +|`generation_number` +|Version number for this answer. Required for Liveboard creation. + +|`question` +|The question executed, useful both for display and to pass it into `createLiveboard`. + +|`data` +|Data in CSV format. You can parse it to show tables or charts. + +|`frame_url` +|Optional. Iframe URL for embedding the visualization in a custom UI. + +|`fields_info` +|Descriptive metadata about the fields and chart, useful for explanations. +|=== + +=== createLiveboard + +Creates a ThoughtSpot Liveboard from one or more answer sessions. + +This is a two-step process and includes the following calls: + +. Call `getAnswer` to generate visualizations and get session data via `session_identifier` and `generation_number`. +. Call `createLiveboard` to create a Liveboard using the session data from step 1. + +==== Query attribute + +[source,TypeScript] +---- +{ + name: string; // Display name for the liveboard + noteTile: string; // Description or note for the liveboard + answers: Array<{ + question: string; + session_identifier: string; + generation_number: number; + }>; +} +---- + +==== Example call + +[source,TypeScript] +---- +const answerData = JSON.parse(answerResult.result.content); + +const liveboardResult = await callMCPTool("createLiveboard", { + name: "My Sales Dashboard", + noteTile: "My Sales Dashboard was created by TS MCP Chat", + answers: [{ + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }] +}); +---- + +==== Response example +[source,JSON] +---- +{ + "liveboardId": "liveboard-guid-here", + "name": "My Sales Dashboard", + "frame_url": "https://..." +} +---- + +===== Key fields +[cols="1,3",options="header"] +|=== +|Field +|Notes + +|`noteTile` +|Required. Do *not* use `description` here. + +|`answers` +|Required array. Each item must include `question`, `session_identifier`, and `generation_number`. + +|`datasourceId` +|Not needed for `createLiveboard`. It is used by `getAnswer`. +|=== + +== Complete workflow examples + +=== Single question → Liveboard + +This example: + +. Gets an answer for a question. +. Parses and validates the response. +. Creates a Liveboard from that answer. + + +[source,TypeScript] +---- +async function createLiveboardFromQuestion(question: string, datasourceId: string) { + try { + // Step 1: Get the answer + console.log("Step 1: Getting answer..."); + const answerResult = await callMCPTool("getAnswer", { + question: question, + datasourceId: datasourceId + }); + + // Step 2: Parse the response + console.log("Step 2: Parsing answer data..."); + const answerData = JSON.parse(answerResult.result.content); + + // Step 3: Validate required fields + if (!answerData.session_identifier) { + throw new Error("Missing session_identifier from getAnswer response"); + } + if (!answerData.generation_number) { + throw new Error("Missing generation_number from getAnswer response"); + } + + // Step 4: Create the liveboard + console.log("Step 4: Creating liveboard..."); + const liveboardResult = await callMCPTool("createLiveboard", { + name: "Sales Analysis", + noteTile: `Created from question: ${question}`, + answers: [{ + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }] + }); + + console.log(" ✓ Liveboard created:", liveboardResult); + return liveboardResult; + } catch (error) { + console.error("Failed to create liveboard:", error); + throw error; + } +} +---- + +=== Query mode – suggested questions → liveboard + +This workflow: + +. Gets AI-suggested questions for a user query. +. Gets answers for each suggested question. +. Creates a Liveboard with all answers [1]. + +[source,TypeScript] +---- +async function queryModeWorkflow(userQuery: string, datasourceId: string) { + // Step 1: Get relevant questions + const relevantQuestionsResult = await callMCPTool("getRelevantQuestions", { + query: userQuery, + datasourceIds: [datasourceId] + }); + + const questionsData = JSON.parse(relevantQuestionsResult.result?.content || "{}"); + const suggestedQuestions = questionsData.questions || []; + + // Step 2: Get answers for each suggested question + const answers = await Promise.all( + suggestedQuestions.map(async (question: string) => { + const answerResult = await callMCPTool("getAnswer", { + question: question, + datasourceId: datasourceId + }); + + const answerData = JSON.parse(answerResult.result?.content || "{}"); + + return { + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }; + }) + ); + + // Step 3: Create liveboard with all answers + const liveboardTitle = `Analysis: ${userQuery}`; + const createLiveboardResult = await callMCPTool("createLiveboard", { + name: liveboardTitle, + noteTile: `${liveboardTitle} was created by TS MCP Chat`, + answers: answers + }); + + return createLiveboardResult; +} +---- + +=== Multi-answer mode – user questions → liveboard + +This workflow: + +. Takes a list of user-provided questions. +. Gets answers for each. +. Creates a Liveboard with all answers [1]. + +[source,TypeScript] +---- +async function multiAnswerWorkflow( + questions: string[], + datasourceId: string, + liveboardTitle: string +) { + // Step 1: Get answers for each question + const answers = await Promise.all( + questions.map(async (question) => { + const answerResult = await callMCPTool("getAnswer", { + question: question, + datasourceId: datasourceId + }); + + const answerData = JSON.parse(answerResult.result?.content || "{}"); + + return { + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }; + }) + ); + + // Step 2: Create liveboard with all answers + const createLiveboardResult = await callMCPTool("createLiveboard", { + name: liveboardTitle, + noteTile: `${liveboardTitle} was created by TS MCP Chat`, + answers: answers + }); + + return createLiveboardResult; +} +---- + +== Error handling +The most frequent issues fall into two categories: + +* Validation errors. For example, missing required fields like `answers`, `session_identifier`, `generation_number`, or `noteTile`, or sending a value with the wrong type. These are returned as structured “invalid_type” errors that tell you which field is wrong and why. + +* Authentication errors, typically HTTP 401 or 403 responses when the `authToken` is invalid or has expired, or when it lacks the required permissions. In such cases, you must obtain a new ThoughtSpot token and retry the call. + +=== Common validation errors + +==== Missing `answers` array + +[source,json] +---- +{ + "code": "invalid_type", + "expected": "array", + "received": "undefined", + "path": ["answers"], + "message": "Required" +} +---- + +Fix: Include the `answers` array when calling `createLiveboard`. + +==== Missing session data +[source,json] +---- +{ + "code": "invalid_type", + "expected": "string", + "received": "undefined", + "path": ["answers", 0, "session_identifier"], + "message": "Required" +} +---- +Fix: Extract `session_identifier` and `generation_number` from the `getAnswer` response and pass them into `answers`. + +==== Missing `noteTile` + +[source,json] +---- +{ + "code": "invalid_type", + "expected": "string", + "received": "undefined", + "path": ["noteTile"], + "message": "Required" +} +---- + +Fix: Include the `noteTile` field. Do not use `description`. + +=== Authentication errors (401 / 403) + +If `/api/mcp` or `/api/search-worksheets` returns 401 or 403, verify whether: + +* `authToken` has expired. +* The token has the right permissions. +* `tsHost` is correct and reachable. + + +== Visual Embed SDK equivalent + +If you use the Visual Embed SDK, the MCP approach is similar but requires additional parameters. + +The Liveboard creation flow works in two different ways: + +* By triggering `HostEvent.CreateLiveboard` on an embedded ThoughtSpot component using the Visual Embed SDK in the browser. +* By calling the `createLiveboard` MCP tool after `getAnswer` through your `/api/mcp` endpoint. This helps you map existing embed-based implementations to the new MCP-based approach without changing the underlying ThoughtSpot behavior. + +=== Visual Embed SDK + +[source,ts] +---- +const result = await embedRef.current.trigger(HostEvent.CreateLiveboard, { + answers: [{ + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }] +}); +---- + +=== MCP SDK via /api/mcp + +[source,ts] +---- +const result = await callMCPTool("createLiveboard", { + name: "Liveboard Name", + noteTile: "Description", + answers: [{ + question: answerData.question, + session_identifier: answerData.session_identifier, + generation_number: answerData.generation_number + }] +}); +---- + +MCP requires `name` and `noteTile` in addition to the `answers` array. + +== Best practices + +* Always call `getAnswer` before `createLiveboard`. + +You cannot create a Liveboard without `session_identifier` and `generation_number`. +* Validate tool responses. + +** Verify that `session_identifier` and `generation_number` exist before creating a Liveboard. +** Log raw MCP responses for debugging. +* Use meaningful Liveboard metadata + +Parameters such as `name` and `noteTile` should make it easy for users to understand what the Liveboard shows and how it was created. +* Search models before querying. +** Use a helper like `/api/search-worksheets` to list available data sources and then pass `datasourceId` into `getRelevantQuestions` / `getAnswer` calls. +* Use `getRelevantQuestions` to discover questions + +For open-ended prompts, use `getRelevantQuestions` to decompose the user's request into concrete analyses. +* Log and monitor. + +Log tool requests and responses, excluding sensitive data, to help debug issues faster. +* Handle errors using the detailed validation messages. + +== Post integration checks +To test the integration, use the following checklist: + +* `getAnswer` returns `session_identifier`. +* `getAnswer` returns `generation_number`. +* `createLiveboard` receives: +** `name` +** `noteTile` (not `description`) +** `answers` array +* Each item in `answers` contains: +** `question` +** `session_identifier` +** `generation_number` +* Liveboard is created successfully in ThoughtSpot. +* `frame_url` is returned for embedding. +* `/api/search-worksheets` returns worksheets successfully. +* `/api/mcp` works for all MCP tools. +* Authentication tokens are properly formatted and valid. + +== Additional resources + +* See also: xref:mcp-integration.adoc[ThoughtSpot MCP Server overview]. +* Check the link:https://github.com/thoughtspot/mcp-server[MCP Server GitHub repo, window=_blank] for implementation instructions. +* In case of issues with connection or authentication, refer to the link:https://github.com/thoughtspot/mcp-server?tab=readme-ov-file#troubleshooting[troubleshooting steps^]. +* For chat client examples, see link:https://github.com/thoughtspot/mcp-chat-client[MCP Chat Client repository, window=_blank]. + + + + + diff --git a/static/doc-images/images/agents-mcp-server-arch.png b/static/doc-images/images/agents-mcp-server-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3993b0b0a0bf34b4cb4b642136f0e7cddd1d0b GIT binary patch literal 188466 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

2ZJxc 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 Date: Fri, 27 Feb 2026 21:13:10 +0530 Subject: [PATCH 22/44] typo fixes --- modules/ROOT/pages/mcp-integration.adoc | 24 ++++++++++++------- .../pages/mcp-server-client-connection.adoc | 6 ++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index 7246df54a..11646f94f 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -14,15 +14,6 @@ Instead of rebuilding analytics logic yourself, you connect an LLM/AI agent to t * Ask questions in natural language * Create Liveboards programmatically from answer sessions -== Get access to MCP Server -The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. + -To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: - -* Enterprise Edition of ThoughtSpot Analytics -* ThoughtSpot Embedded - -To learn more about subscription options, contact your ThoughtSpot Sales representative. - == Spotter and MCP Server ThoughtSpot provides three main options to integrate AI and analytics: @@ -49,8 +40,19 @@ Use ThoughtSpot MCP Server when: | Spotter APIs | Provides REST APIs for full programmatic control over analytics workflows. You can use your own LLM and orchestration logic to interact with Spotter and retrieve structured answers, charts, or relevant questions for a specific data model. +|| |=== +=== Access to MCP Server +The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. + +To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: + +* Enterprise Edition of ThoughtSpot Analytics +* ThoughtSpot Embedded + +To learn more about subscription options, contact your ThoughtSpot Sales representative. + + == Architecture and roles A typical implementation with ThoughtSpot MCP Server includes the following core components: @@ -72,6 +74,7 @@ a| Acts as orchestrator - Wraps AI REST APIs. |*Client Interface* a| The user-facing interface that renders chat, responses, and charts. For example, Claude AI web app, Claude Desktop, ChatGPT or OpenAI integrations, Gemini-based agents, custom web applications, or internal tools. +|| |====== The interaction between the user, agent, and MCP Server is illustrated in the following figure: @@ -79,6 +82,9 @@ The interaction between the user, agent, and MCP Server is illustrated in the fo [.widthAuto] image::./images/agents-mcp-server-arch.png[MCP integration] + + + == How it works The MCP Server implementation with an agentic framework or LLM client typically involves the following workflow: diff --git a/modules/ROOT/pages/mcp-server-client-connection.adoc b/modules/ROOT/pages/mcp-server-client-connection.adoc index 67be7823b..47fcc6708 100644 --- a/modules/ROOT/pages/mcp-server-client-connection.adoc +++ b/modules/ROOT/pages/mcp-server-client-connection.adoc @@ -717,7 +717,7 @@ Fix: Extract `session_identifier` and `generation_number` from the `getAnswer` r Fix: Include the `noteTile` field. Do not use `description`. -=== Authentication errors (401 / 403) +=== Authentication errors If `/api/mcp` or `/api/search-worksheets` returns 401 or 403, verify whether: @@ -774,8 +774,8 @@ You cannot create a Liveboard without `session_identifier` and `generation_numbe ** Log raw MCP responses for debugging. * Use meaningful Liveboard metadata + Parameters such as `name` and `noteTile` should make it easy for users to understand what the Liveboard shows and how it was created. -* Search models before querying. -** Use a helper like `/api/search-worksheets` to list available data sources and then pass `datasourceId` into `getRelevantQuestions` / `getAnswer` calls. +* Search models before querying. + +Use a helper like `/api/search-worksheets` to list available data sources and then pass `datasourceId` into `getRelevantQuestions` / `getAnswer` calls. * Use `getRelevantQuestions` to discover questions + For open-ended prompts, use `getRelevantQuestions` to decompose the user's request into concrete analyses. * Log and monitor. + From f7ee501f59094cabe1e6664e6fd85f0da0dbc58a Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 21:16:49 +0530 Subject: [PATCH 23/44] typo fixes --- modules/ROOT/pages/mcp-integration.adoc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index 11646f94f..ab3c913eb 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -43,9 +43,8 @@ Use ThoughtSpot MCP Server when: || |=== -=== Access to MCP Server -The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. + -To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: +=== Access to MCP Server +The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: * Enterprise Edition of ThoughtSpot Analytics * ThoughtSpot Embedded From ab6320339fd06a62402c5c882c520d50073414af Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 09:05:48 +0530 Subject: [PATCH 24/44] 0cp --- modules/ROOT/pages/webhooks-lb-schedule.adoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ROOT/pages/webhooks-lb-schedule.adoc b/modules/ROOT/pages/webhooks-lb-schedule.adoc index 846fdec04..68a208ebc 100644 --- a/modules/ROOT/pages/webhooks-lb-schedule.adoc +++ b/modules/ROOT/pages/webhooks-lb-schedule.adoc @@ -1002,9 +1002,15 @@ Along with the JSON payload, if the Liveboard schedule is configured to send a P The payload also includes file attachments in the file format specified in the Liveboard schedule. The file format can be PDF, CSV, or XLSX. === Response after webhook delivery +<<<<<<< HEAD The webhook endpoint must respond with an HTTP 2xx status code to confirm successful receipt and processing of the request. The receiving server must send a 2xx response within 5 seconds of the webhook delivery. If your server takes longer than 5 seconds to respond, returns a 4xx error, or times out, ThoughtSpot may still deliver the Liveboard data and file. However, the notification status will be recorded as `FAILED` in the ThoughtSpot notification history and request will be retried. +======= +The webhook must return an HTTP 2xx status code within 5 seconds to indicate successful receipt and processing. Your server should therefore respond with a 2xx status within this timeframe after receiving the webhook delivery. All 2xx responses are recorded as `SUCCESS`. + +If your server takes longer than that to respond, returns a 4xx error or times out, ThoughtSpot may still deliver the Liveboard data and file. However, the notification status is recorded as `FAILED` in the ThoughtSpot notification history. +>>>>>>> 50d59692 (cp) To ensure a timely response, we recommend processing webhook payloads asynchronously. Your server can immediately return a 2xx response upon receipt of the webhook and then handle the payload in the background without blocking subsequent webhook deliveries. From 31c3c78337634a8e3278cbcc2ecbcde0f7e87eba Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Sat, 28 Feb 2026 09:44:06 +0530 Subject: [PATCH 25/44] edits and typo fixes --- modules/ROOT/pages/mcp-integration.adoc | 136 ++++++------------ .../pages/mcp-server-client-connection.adoc | 59 ++++---- 2 files changed, 76 insertions(+), 119 deletions(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index ab3c913eb..a7f222195 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -1,4 +1,4 @@ -= ThoughtSpot MCP Server += Using ThoughtSpot MCP Server :toc: true :toclevels: 3 @@ -6,44 +6,43 @@ :page-pageid: mcp-integration :page-description: Learn how to use the ThoughtSpot Model Context Protocol (MCP) server to interact with ThoughtSpot data via MCP tools -ThoughtSpot’s Agentic Model Context Protocol (MCP) Server allows you to integrate ThoughtSpot analytics into any AI agent, custom chatbot, or LLM platform that supports MCP. +ThoughtSpot’s Agentic Model Context Protocol (MCP) Server allows you to integrate ThoughtSpot analytics into any AI agent, custom chatbot, or LLM platform that supports MCP. Instead of rebuilding analytics logic yourself, you connect an LLM/AI agent to the ThoughtSpot MCP Server. -Instead of rebuilding analytics logic yourself, you connect an LLM/AI agent to the ThoughtSpot MCP Server. The LLM can then: +When integrated, the MCP Server equips your client and AI agent/LLM with the following capabilities: -* Discover ThoughtSpot MCP tools automatically -* Ask questions in natural language -* Create Liveboards programmatically from answer sessions +* Discovering ThoughtSpot MCP tools automatically +* Natural language queries and responses +* Programmatic creation of Liveboards == Spotter and MCP Server -ThoughtSpot provides three main options to integrate AI and analytics: +ThoughtSpot provides the following options to integrate AI and analytics into your workflows: [cols="1,3",options="header"] |=== | Feature | Purpose - -|**Spotter Embed** -| Allows embedding Spotter conversational UI and agentic capabilities directly into your application using the Visual Embed SDK. -Requires minimal development effort and supports faster deployment. This option is recommended for conversational analytics experiences within your application context. - | **ThoughtSpot MCP Server** -a| Allows using your own UI, agent or LLM, and orchestration. ThoughtSpot exposes governed analytics tools via the MCP protocol, allowing your agent to discover and call tools programmatically. This method is recommended for using ThoughtSpot as a plug-in analytics engine in your AI experience. +a| Allows using your own UI, agent, or LLM and orchestration. ThoughtSpot exposes governed analytics tools via the MCP protocol, allowing your agent to discover and call tools programmatically. + -Use ThoughtSpot MCP Server when: +|**Spotter Embed** +|Allows xref:embed-spotter.adoc[embedding Spotter conversational UI and agentic capabilities] directly into your application using the Visual Embed SDK. + +Requires minimal development effort and supports faster deployment. + +Recommended for integrating conversational analytics experiences within your application context. -* Integrating with agents that natively support MCP, such as Claude, OpenAI, Gemini, or custom MCP clients. +|**Spotter APIs** +|Provides xref:spotter-apis.adoc[REST APIs] for programmatic control of analytics and agentic workflows. You can use your own LLM and orchestration logic to interact with Spotter, retrieve structured answers, charts, or relevant questions for a specific data model. +|| +|=== -* You want to reuse ThoughtSpot’s governed analytics and data security controls such as Row-level Security (RLS), Column-level Security (CLS) rules, Liveboards, and data modeling options, instead of building analytics logic in your application. +=== MCP Server use cases -* You want the LLM to discover and call tools via the MCP protocol, rather than connecting every endpoint manually. +ThoughtSpot recommends using MCP Server for the following business scenarios: +* When you want to use ThoughtSpot as a plug-in analytics engine within your AI or agentic experience. This enables you to leverage ThoughtSpot’s governed analytics, including Row-level Security (RLS), Column-level Security (CLS), Liveboards, and data modeling, rather than building analytics logic directly into your application. +* When you are working with agents or LLMs that natively support the MCP protocol, such as Claude, OpenAI, Gemini, or custom MCP clients. +* When you want your LLM or agent to discover and call tools via the MCP protocol, allowing for dynamic tool discovery and orchestration, instead of manually connecting to each endpoint. -| Spotter APIs -| Provides REST APIs for full programmatic control over analytics workflows. You can use your own LLM and orchestration logic to interact with Spotter and retrieve structured answers, charts, or relevant questions for a specific data model. -|| -|=== - -=== Access to MCP Server +=== MCP Server access The ThoughtSpot MCP Server is an add-on feature available with the link:https://www.thoughtspot.com/pricing[ThoughtSpot Analytics and ThoughtSpot Embedded offerings, window=_blank]. To purchase the MCP Server subscription and enable the MCP Server in your environment, you must have an active subscription to one of the following ThoughtSpot license plans: * Enterprise Edition of ThoughtSpot Analytics @@ -51,17 +50,15 @@ The ThoughtSpot MCP Server is an add-on feature available with the link:https:// To learn more about subscription options, contact your ThoughtSpot Sales representative. - == Architecture and roles - -A typical implementation with ThoughtSpot MCP Server includes the following core components: +The MCP Server integration and orchestration layer includes the following core components: [width="100%" cols="2,4"] [options='header'] |====== |Component|Role |*Agent or LLM* -a| Acts as orchestrator +a| Acts as orchestrator. - Receives the user’s prompt. - Discovers ThoughtSpot MCP tools via the MCP protocol. - Decides which tools to call and in what order. @@ -76,89 +73,57 @@ The user-facing interface that renders chat, responses, and charts. For example, || |====== -The interaction between the user, agent, and MCP Server is illustrated in the following figure: +The following figure illustrates the interaction between the user, agent, and MCP Server: [.widthAuto] image::./images/agents-mcp-server-arch.png[MCP integration] - - - == How it works -The MCP Server implementation with an agentic framework or LLM client typically involves the following workflow: +The MCP Server integration and orchestration in agentic environments typically involves the following workflows: . *User asks a question* + User sends a query in the chat interface to get data. For example, `What were the total sales of Jackets and Bags in the Northeast last year?` + -+ Optionally, the user can specify the data context so that data from a specific source is used to generate answers. -. *(Optional) Data source selection via `getDataSourceSuggestions`* + -If the question doesn’t specify a data source, the agent can call `getDataSourceSuggestions`. -+ -ThoughtSpot returns multiple candidate data sources (models) with confidence scores and reasoning. +. *Agent calls `getDataSourceSuggestions`* (Optional) + +If the user's question doesn’t specify a data source, the agent can call `getDataSourceSuggestions`. In response to this request, ThoughtSpot returns multiple candidate data sources (models) with confidence scores and reasoning. -. **Query decomposition** + -The user's query is decomposed into smaller questions via `getRelevantQuestions`. -+ -The agent calls `getRelevantQuestions` with the following parameters: + -* The user query (`query`) -* One or more `datasourceIds` -+ -ThoughtSpot returns the AI-suggested, schema-aware questions that are easier to run analytically. +. **User's query is decomposed into sub-questions** + +To decompose the user's query into smaller questions, the agent calls `getRelevantQuestions`. + +In response to the agent's request, ThoughtSpot returns the AI-suggested, schema-aware questions that are easier to run analytically. -. *Answer generation via `getAnswer`* + -For each suggested or chosen question, the agent calls `getAnswer` with: -+ -* `question` -* `datasourceId` -+ -ThoughtSpot returns the following: -+ +. *Query is processed for generating answers* + +For each suggested or chosen question, the agent calls `getAnswer`. In response to this request, ThoughtSpot returns the following: + * Preview data (CSV string) for LLM reasoning. * Visualization metadata, including an embeddable `frame_url`. * `session_identifier` and `generation_number` for charts that are used for creating Liveboards. -. *(Optional) Liveboard creation via `createLiveboard`* + -To save one or more answers as a Liveboard: -+ -* The agent extracts `question`, `session_identifier`, and `generation_number` from each `getAnswer` response. -* Calls `createLiveboard` with: -** `name` – Name of the Liveboard. -** `noteTile` – descriptive HTML text shown as a note tile. -** `answers` – array of answers from `getAnswer`. -+ +. *Creates a Liveboard using metadata from query response* (Optional) + +The user can choose to save answers from query responses in a ThoughtSpot Liveboard. For this workflow, the agent extracts `question`, `session_identifier`, and `generation_number` from each `getAnswer` response and calls `createLiveboard`. + ThoughtSpot creates a Liveboard and returns identifiers and a `frame_url` for the Liveboard. -. *User experience for chat sessions* -+ -In MCP platforms such as Claude and ChatGPT, users typically see a natural-language summary with a link to a ThoughtSpot Liveboard. -+ -In custom apps, you can: - -* Embed the `frame_url` in iframes to show interactive charts inline. -* Provide Call To Action (CTA) elements backed by `createLiveboard`. +During this interaction, users on MCP platforms such as Claude and ChatGPT typically see a natural-language summary with a link to a ThoughtSpot Liveboard. -== Authentication for MCP Server +In custom apps, you can embed the `frame_url` generated from this interaction in iframes to show interactive charts inline and provide Call To Action (CTA) elements backed by `createLiveboard` if required. -The MCP Server always runs under an authenticated ThoughtSpot user context. You can authenticate in two main ways: +== User authentication +When integrated, the MCP Server runs under an authenticated ThoughtSpot user context. You can choose one of the following authentication options: OAuth:: -Use OAuth when: -* Connecting plug-and-play MCP platforms such as Claude, Gemini, ChatGPT integrations, and more. -* You want the platform to drive a browser-based sign-in flow. +Use OAuth in the following scenarios: +* When connecting plug-and-play MCP platforms, such as Claude, Gemini, ChatGPT, and others. +* When you want the platform to drive a browser-based sign-in flow. + In a typical OAuth flow: - . ThoughtSpot MCP Server is configured as an MCP tool/connection in the client. . Client redirects the user to ThoughtSpot to sign in. . Client stores the OAuth token and passes it to the MCP Server on each tool call. Token-based trusted authentication:: -Use trusted authentication if: - -* You are building a custom UI or custom MCP client. -* You handle user identity and want seamless SSO from your app into ThoughtSpot. +Use trusted authentication in the following scenarios: +* If you are building a custom UI or custom MCP client. +* If your setup has a backend component, and your organization handles user identity and wants seamless SSO from your app into ThoughtSpot. + In a typical trusted authentication flow: @@ -168,15 +133,8 @@ In a typical trusted authentication flow: ** `GET /api/rest/2.0/auth/session/token`. . The token generated for the user session is then passed to the MCP Server as a bearer token header, or as part of the client's MCP server configuration. For example, `authorization_token` for Claude. -//// -[NOTE] -==== -Trusted authentication tokens used only as HTTP headers do not create a browser session by themselves. For embedded sessions, you may also need a cookie. -==== -//// - -== Connecting MCP clients -For information about supported platforms, how to connect MCP clients, examples, and best practices, see xref:mcp-server-client-connection.adoc[Connect clients to ThoughtSpot MCP server]. +== Integration with MCP platforms and client environments +For information about the supported MCP platforms, instructions on how to connect MCP clients, code examples, and best practices, see xref:mcp-server-client-connection.adoc[Connecting clients to ThoughtSpot MCP server]. == Additional resources diff --git a/modules/ROOT/pages/mcp-server-client-connection.adoc b/modules/ROOT/pages/mcp-server-client-connection.adoc index 47fcc6708..49875ca45 100644 --- a/modules/ROOT/pages/mcp-server-client-connection.adoc +++ b/modules/ROOT/pages/mcp-server-client-connection.adoc @@ -1,4 +1,4 @@ -= Connect clients to ThoughtSpot MCP Server += Connecting clients to ThoughtSpot MCP Server :toc: true :toclevels: 3 @@ -6,24 +6,26 @@ :page-pageid: connect-mcp-server-to-clients :page-description: Learn how to connect ThoughtSpot MCP server to clients and call tools -To connect clients to the ThoughtSpot MCP server, add the MCP server endpoint to your LLM client. +To connect ThoughtSpot MCP server to your environment, add the MCP server endpoint to your LLM client's configuration settings. -Authentication is handled per user, typically using OAuth. When connected, the client can discover available MCP tools, select data sources, and interact with ThoughtSpot analytics by calling MCP tools. The MCP server exposes only the data sources and functions the authenticated user is allowed to access, and all actions are performed in the context of that user’s security entitlements. +Authentication is handled per user, typically using xref:mcp-integration.adoc#_user_authentication[OAuth or trusted authentication tokens]. + +When connected, the client can discover available MCP tools, select data sources, and interact with ThoughtSpot analytics by calling MCP tools. The MCP Server exposes only the data sources and functions that the authenticated user is allowed to access, and all actions are performed in the context of that user's security entitlements defined in the ThoughtSpot instance. == Before you begin -Before you begin, check the following prerequisites and ensure that the required configuration and setup are available for connecting your client to the ThoughtSpot MCP server. +Before you begin, review the following prerequisites and ensure that the required configuration and setup are available for integration: -* Node.js version 22 or later is installed for node-based examples and local clients. -* A ThoughtSpot application instance with 10.11.0.cl or a later release version. -* Users have the necessary privileges to view data from relevant models/tables in ThoughtSpot. Existing RLS/CLS rules on tables are enforced automatically in data source responses. -* For Answer and Liveboard creation, the user must have the data download and content-creation privileges. +* Ensure that Node.js version 22 or later is installed for node-based examples and local clients. +* Your setup has access to a ThoughtSpot application instance with 10.11.0.cl or a later release version. +* Your users have the necessary privileges to view data from relevant models and tables in ThoughtSpot. Existing RLS/CLS rules on tables are enforced automatically in data source responses. +* For Answer and Liveboard creation, the user must have the data download and content creation privileges. -== Connecting Remote MCP-aware clients +== Connecting remote MCP-aware clients If you are using a client that supports remote MCPs natively, use the following MCP server endpoint: `https://agent.thoughtspot.app/mcp` -For clients that require a bearer token for authentication: +For clients that require a bearer token for authentication, use the following URL format: `https://agent.thoughtspot.app/bearer/mcp` @@ -31,9 +33,9 @@ For OpenAI MCP and Responses API integration, use the following URL: `https://agent.thoughtspot.app/openai/mcp` -For additional information, refer to your client’s documentation for how to register a remote MCP server. +For additional information about how to register a remote MCP server, refer to your client’s documentation. -Once registered, the agent discovers ThoughtSpot tools from the MCP endpoint and calls tools such as `getRelevantQuestions`, `getAnswer`, and `createLiveboard` as needed to answer the user's question. +When the MCP Server is connected, the AI agent or LLM in your environment automatically discovers ThoughtSpot tools from the MCP endpoint and calls tools as needed. === Claude MCP connector @@ -64,10 +66,9 @@ curl https://api.anthropic.com/v1/messages \ }' ---- - === OpenAI Responses API (MCP tools) -The following example shows the code to connect OpenAI to the MCP server: +The following example shows how to connect OpenAI to the MCP server: [source,bash] ---- @@ -93,7 +94,7 @@ curl https://api.openai.com/v1/responses \ === Gemini with MCP -The following example shows the code to connect Gemini to the MCP server: +The following example shows the code required to connect Gemini to the MCP server: [source,typescript] ---- @@ -137,9 +138,8 @@ console.log(response.text); await mcpClient.close(); ---- -== Connecting clients that do not natively support remote MCP servers - -Some clients that do not natively support configuring a remote MCP URL may require an `mcp-remote` component. In such cases, configure the MCP server as shown in this example: +== Connecting clients that do not support remote MCP servers +Some clients that do not natively support configuring a remote MCP server URL may require an `mcp-remote` component. In such cases, configure the MCP server as shown in this example: [source,JSON] ---- @@ -158,7 +158,7 @@ Some clients that do not natively support configuring a remote MCP URL may requi == Internal API routes -If you are building your own web app or chatbot, you might want to set up internal routes that act as messengers, passing requests from your app to the ThoughtSpot MCP Server or REST APIs and then returning the results to your app. This allows your app to fetch data or answers from ThoughtSpot without connecting to it directly. +If you are building your own web app or chatbot, you might want to set up internal routes that act as messengers, passing requests from your app to the ThoughtSpot MCP Server or REST APIs, and then returning the results to your app. This allows your app to fetch data or answers from ThoughtSpot without connecting to it directly. The sample patterns in the following examples define two internal routes for a Next.js app. You can adapt this pattern to other backend frameworks: @@ -168,7 +168,6 @@ Forwards tool calls to the ThoughtSpot MCP server. Searches models using the ThoughtSpot REST API. === POST /api/mcp - Serves as a proxy endpoint for calling ThoughtSpot MCP tools from the client side. This route forwards requests from your frontend to the ThoughtSpot MCP tools. ==== Request parameters @@ -247,8 +246,8 @@ This route searches for data sources such as models in ThoughtSpot using the RES |=== | Parameter | Required | Description | `tsHost` | Yes | __String__. ThoughtSpot instance URL -| `authToken` | Yes |__String__. Bearer token for authentication -| `namePattern` |No |__String__. Pattern to filter model names. Default is `*`. +| `authToken` | Yes | __String__. Bearer token for authentication. +| `namePattern` | No | __String__. Pattern to filter model names. Default is `*`. |=== ==== Example request @@ -352,7 +351,7 @@ Gets AI-suggested analytical questions relevant to the user’s query for a give ==== Query attributes -[source,Typescript] +[source,TypeScript] ---- { query: string; // User's natural language query @@ -362,7 +361,7 @@ Gets AI-suggested analytical questions relevant to the user’s query for a give ==== Example call -[source,Typescript] +[source,TypeScript] ---- const result = await callMCPTool("getRelevantQuestions", { query: "show me sales data", @@ -376,7 +375,7 @@ const result = await callMCPTool("getRelevantQuestions", { ---- { "questions": [ - "What is the total sales by region?", + "What are total sales by region?", "Which products have the highest revenue?", "What are the top selling categories?" ] @@ -458,7 +457,7 @@ This is a two-step process and includes the following calls: . Call `getAnswer` to generate visualizations and get session data via `session_identifier` and `generation_number`. . Call `createLiveboard` to create a Liveboard using the session data from step 1. -==== Query attribute +==== Query attributes [source,TypeScript] ---- @@ -573,11 +572,11 @@ async function createLiveboardFromQuestion(question: string, datasourceId: strin === Query mode – suggested questions → liveboard -This workflow: +This workflow includes the following steps: . Gets AI-suggested questions for a user query. . Gets answers for each suggested question. -. Creates a Liveboard with all answers [1]. +. Creates a Liveboard with all answers. [source,TypeScript] ---- @@ -623,11 +622,11 @@ async function queryModeWorkflow(userQuery: string, datasourceId: string) { === Multi-answer mode – user questions → liveboard -This workflow: +This workflow includes the following steps: . Takes a list of user-provided questions. . Gets answers for each. -. Creates a Liveboard with all answers [1]. +. Creates a Liveboard with all answers. [source,TypeScript] ---- From 436ff43507cc418c7635db62d89cf7fbc4c3e814 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Sat, 28 Feb 2026 09:57:53 +0530 Subject: [PATCH 26/44] edits --- modules/ROOT/pages/common/nav.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/common/nav.adoc b/modules/ROOT/pages/common/nav.adoc index eedcec0e2..9f9ac587d 100644 --- a/modules/ROOT/pages/common/nav.adoc +++ b/modules/ROOT/pages/common/nav.adoc @@ -221,8 +221,8 @@ include::generated/typedoc/CustomSideNav.adoc[] ** link:{{navprefix}}/SpotterCode[SpotterCode for IDEs] *** link:{{navprefix}}/integrate-SpotterCode[Integrating SpotterCode] *** link:{{navprefix}}/spottercode-prompting-guide[SpotterCode prompting guide] -** link:{{navprefix}}/mcp-integration[ThoughtSpot MCP server] -*** link:{{navprefix}}/connect-mcp-server-to-clients[Connect clients to ThoughtSpot MCP Server] +** link:{{navprefix}}/mcp-integration[Using ThoughtSpot MCP server] +*** link:{{navprefix}}/connect-mcp-server-to-clients[Connecting clients to MCP Server] * link:{{navprefix}}/development-and-deployment[Deployment and integration] ** link:{{navprefix}}/development-and-deployment[Development and deployment] From 231ee9e7b73d93b57dd0fa06cb7fe80afc6b72d4 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Sat, 28 Feb 2026 10:01:32 +0530 Subject: [PATCH 27/44] edits --- modules/ROOT/pages/mcp-integration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index a7f222195..996bf6f68 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -82,7 +82,7 @@ image::./images/agents-mcp-server-arch.png[MCP integration] The MCP Server integration and orchestration in agentic environments typically involves the following workflows: . *User asks a question* + -User sends a query in the chat interface to get data. For example, `What were the total sales of Jackets and Bags in the Northeast last year?` + +A user sends a query in the chat interface to get data. For example, `What were the total sales of Jackets and Bags in the Northeast last year?` + Optionally, the user can specify the data context so that data from a specific source is used to generate answers. . *Agent calls `getDataSourceSuggestions`* (Optional) + From b07b66899ac66ecfb14a6fa9dc2120895cc18e79 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Sat, 28 Feb 2026 10:16:11 +0530 Subject: [PATCH 28/44] edits --- modules/ROOT/pages/mcp-server-client-connection.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/mcp-server-client-connection.adoc b/modules/ROOT/pages/mcp-server-client-connection.adoc index 49875ca45..9940723b9 100644 --- a/modules/ROOT/pages/mcp-server-client-connection.adoc +++ b/modules/ROOT/pages/mcp-server-client-connection.adoc @@ -66,7 +66,7 @@ curl https://api.anthropic.com/v1/messages \ }' ---- -=== OpenAI Responses API (MCP tools) +=== OpenAI Responses API The following example shows how to connect OpenAI to the MCP server: @@ -369,7 +369,7 @@ const result = await callMCPTool("getRelevantQuestions", { }); ---- -==== Parsed response example +==== Response example [source,json] ---- @@ -570,7 +570,7 @@ async function createLiveboardFromQuestion(question: string, datasourceId: strin } ---- -=== Query mode – suggested questions → liveboard +=== Query mode → suggested questions → Liveboard This workflow includes the following steps: @@ -620,7 +620,7 @@ async function queryModeWorkflow(userQuery: string, datasourceId: string) { } ---- -=== Multi-answer mode – user questions → liveboard +=== Multi-answer mode → user questions → liveboard This workflow includes the following steps: @@ -673,7 +673,7 @@ The most frequent issues fall into two categories: === Common validation errors -==== Missing `answers` array +==== Missing answers array [source,json] ---- @@ -701,7 +701,7 @@ Fix: Include the `answers` array when calling `createLiveboard`. ---- Fix: Extract `session_identifier` and `generation_number` from the `getAnswer` response and pass them into `answers`. -==== Missing `noteTile` +==== Missing noteTile [source,json] ---- From 962103448c9d56554c1c64377cc36b5e94f2f5a4 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Sat, 28 Feb 2026 10:29:40 +0530 Subject: [PATCH 29/44] edits --- modules/ROOT/pages/mcp-integration.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/mcp-integration.adoc b/modules/ROOT/pages/mcp-integration.adoc index 996bf6f68..a72e9c5dd 100644 --- a/modules/ROOT/pages/mcp-integration.adoc +++ b/modules/ROOT/pages/mcp-integration.adoc @@ -93,10 +93,11 @@ To decompose the user's query into smaller questions, the agent calls `getReleva In response to the agent's request, ThoughtSpot returns the AI-suggested, schema-aware questions that are easier to run analytically. . *Query is processed for generating answers* + -For each suggested or chosen question, the agent calls `getAnswer`. In response to this request, ThoughtSpot returns the following: + -* Preview data (CSV string) for LLM reasoning. +For each suggested or chosen question, the agent calls `getAnswer`. + +In response to this request, ThoughtSpot returns the following: + +* Preview data for LLM reasoning. * Visualization metadata, including an embeddable `frame_url`. -* `session_identifier` and `generation_number` for charts that are used for creating Liveboards. +* `session_identifier` and `generation_number` for charts that are required for creating Liveboards. . *Creates a Liveboard using metadata from query response* (Optional) + The user can choose to save answers from query responses in a ThoughtSpot Liveboard. For this workflow, the agent extracts `question`, `session_identifier`, and `generation_number` from each `getAnswer` response and calls `createLiveboard`. + From 04611103658fb8b3e9965de44211edf0ad7adabc Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:18:34 -0600 Subject: [PATCH 30/44] Update abac_rls-variables.adoc Initial reorganization begun - simplified the intro be shorter and more direct --- modules/ROOT/pages/abac_rls-variables.adoc | 54 +++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 13155c0cf..86d15dc7a 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -6,17 +6,37 @@ :page-pageid: abac-via-rls-variables :page-description: Attribute-based access control pattern with variable attributes referenced in Row-Level Security (RLS) rules on a given table. -Attribute-Based Access Control (ABAC) is an access control model in which security entitlements are determined by evaluating a set of attributes included in a token generated for a user. The attributes are passed in a JSON Web Token (JWT) at session creation to dynamically filter data and enable user-specific security policies. +Attribute-Based Access Control (ABAC) is an access control model in which security entitlements are assigned to a ThoughtSpot user directly as a set of attributes with lists of values, rather than relying on a JOINed entitlements table within the data model. + +ThoughtSpot allows assigning attribute values to a user at session creation time by adding the values to the user's access token request. + +RLS Rules are defined on the table objects, which binds dynamically generated `WHERE` clauses to any generated query. Within the RLS Rules, the attributes are referenced by their variable names using the `ts_var()` function to dynamically filter data and enable user-specific security policies. == Overview -To implement data security for application users, administrators can define RLS rules that use system variables such as `ts_username` or `ts_groups`. If a ThoughtSpot deployment requires granular access control and dynamic assignments beyond what system variables can support, administrators can use formula variables within RLS rules. +RLS rules have a defined set of system variables such as `ts_username` or `ts_groups`. Once ABAC via RLS is enabled, the `ts_var()` formula is available within the RLS rule editor to refer to any defined Formula variable within ThoughtSpot. -Formula variables are custom variables that enable dynamic and context-aware logic in RLS rules. They can be assigned at the Org, user, and data model levels. +Formula variables are custom variables defined within ThoughtSpot that enable dynamic and context-aware logic in RLS rules. They are assigned at the user level during session creation for the ABAC pattern, although they can be set at Org and data model levels as well. +//// In embedded analytics scenarios, where each user may require different data access, administrators can assign security attributes and rules on a per-user basis. For these use cases, administrators can implement a JWT-based ABAC model combined with RLS to enforce data security using dynamic attributes derived from formula variables. In the ABAC via RLS with variables method, administrators add formula variables to RLS rules and pass their values (`variable_values`) as security attributes and entitlements to the user session through a JWT. All derived objects then inherit the data security rules from the underlying Table and are filtered according to the user’s entitlements provided in the token. +//// + +=== Implementation steps +The ABAC implementation with formula variables and RLS rules includes the following steps: + +* xref:abac_rls-variables.adoc#_create_formula_variables[Creating formula variables] + +To generate tokens with variable attributes, the variables must be available in ThoughtSpot. To create variables, use the xref:variables.adoc#_create_a_variable[Variable REST API]. +* xref:abac_rls-variables.adoc#_add_rls_rules_with_variable_references[Adding RLS rules with formula variables] + +When defining an RLS rule with variables, use the `ts_var` function. These RLS rules will apply to the Models, Liveboards, and other objects derived from that Table. +* xref:abac_rls-variables.adoc#_define_values_and_scope_for_variables[Creating a token request with variable attributes] + +To generate a JWT token, use the `/api/rest/2.0/auth/token/custom` REST API endpoint. The token generation request must include the variable attributes, which will be used for RLS evaluation to enable per‑user entitlements and data filters across all the objects derived from the Table. +* xref:abac_rls-variables.adoc#_verify_the_entitlements[Verifying entitlements] + +To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. + +The ABAC via tokens method requires the xref:trusted-authentication.adoc[trusted authentication] setup. [NOTE] ==== @@ -37,36 +57,8 @@ Several features within ThoughtSpot, such as autocompletion in Search on values * Disable indexing for columns and fields that must be restricted by ABAC. You may also want to disable indexing on all sensitive columns. * Define an RLS rule on those fields, since RLS is enforced at the indexing layer and will secure suggestions and sample values. -=== Implementation steps -The ABAC implementation with formula variables and RLS rules includes the following steps: -* xref:abac_rls-variables.adoc#_create_formula_variables[Creating formula variables] + -To generate tokens with variable attributes, the variables must be available in ThoughtSpot. To create variables, use the xref:variables.adoc#_create_a_variable[Variable REST API]. -* xref:abac_rls-variables.adoc#_add_rls_rules_with_variable_references[Adding RLS rules with formula variables] + -When defining an RLS rule with variables, use the `ts_var` function. These RLS rules will apply to the Models, Liveboards, and other objects derived from that Table. -* xref:abac_rls-variables.adoc#_define_values_and_scope_for_variables[Creating a token request with variable attributes] + -To generate a JWT token, use the `/api/rest/2.0/auth/token/custom` REST API endpoint. The token generation request must include the variable attributes, which will be used for RLS evaluation to enable per‑user entitlements and data filters across all the objects derived from the Table. -* xref:abac_rls-variables.adoc#_verify_the_entitlements[Verifying entitlements] + -To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. - -The ABAC via tokens method requires the xref:trusted-authentication.adoc[trusted authentication] setup. -//// -=== Mandatory token filters - -The `is_mandatory_token_filter: true` setting in object TML enforces that a filter rule must be provided for a specific column. When this attribute is set on a column in a Model, ThoughtSpot will deny all data access for users who do not have a corresponding filter rule for that column in their ABAC token. - -When setting filter rules within the token, you must place the `is_mandatory_token_filter: true` property on every column in a Model where a filter rule is expected. This setting will deny any access to data if a user has not been assigned values for the expected set of fields. - -[#column-name-warning] -The filter rules require passing the *exact* column name as defined in the Model. Otherwise, the values will not bind to any column. You must coordinate between the team that maintains the data objects and the team that builds the xref:trusted-auth-token-request-service.adoc[token request service] to know if any changes will be made to a Model and to ensure column names remain consistent. + -For this reason, end users of an embedded app must not be granted edit access to any Model using ABAC rules via tokens. Setting the `is_mandatory_token_filter: true` property on every column where a filter rule is expected ensures that no data is returned for users when column names change. - -[NOTE] -==== -If a column is set with both `is_hidden: true` and `is_mandatory_token_filter: true`, and filter conditions for that column are defined in the ABAC token, the filter will be applied as expected. The column will be hidden from the user interface, but the mandatory filter requirement will still be enforced, and data will be shown according to the filter values provided in the token. -==== -//// == Create formula variables From b6d69d6f846e15972178e85050ef8366c3fe9a12 Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:31:17 -0600 Subject: [PATCH 31/44] Update abac_rls-variables.adoc More rearrangement to put things closer to where they are relevant --- modules/ROOT/pages/abac_rls-variables.adoc | 37 ++++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 86d15dc7a..cc0abc91f 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -25,6 +25,11 @@ In the ABAC via RLS with variables method, administrators add formula variables //// === Implementation steps +[NOTE] +==== +Formula variables are available on ThoughtSpot starting from 10.15.0.cl. If this feature is not enabled on your instance, contact ThoughtSpot Support. +==== + The ABAC implementation with formula variables and RLS rules includes the following steps: * xref:abac_rls-variables.adoc#_create_formula_variables[Creating formula variables] + @@ -34,22 +39,9 @@ When defining an RLS rule with variables, use the `ts_var` function. These RLS r * xref:abac_rls-variables.adoc#_define_values_and_scope_for_variables[Creating a token request with variable attributes] + To generate a JWT token, use the `/api/rest/2.0/auth/token/custom` REST API endpoint. The token generation request must include the variable attributes, which will be used for RLS evaluation to enable per‑user entitlements and data filters across all the objects derived from the Table. * xref:abac_rls-variables.adoc#_verify_the_entitlements[Verifying entitlements] + -To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. - -The ABAC via tokens method requires the xref:trusted-authentication.adoc[trusted authentication] setup. - -[NOTE] -==== +To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. Variables and their values are visible to administrators using the `/api/rest/2.0/template/variables/search` endpoint with the `response_content` parameter set to `METADATA_AND_VALUES` -* Formula variables are available on ThoughtSpot starting from 10.15.0.cl. If this feature is not enabled on your instance, contact ThoughtSpot Support. -* In the legacy JWT ABAC token requests, you could set the `persist_option` parameter to `NONE`, `APPEND`, `REPLACE`, and `RESET`. However, when you use the `variable_values` parameter in the JWT API token request to set up RLS values for a given user, only `APPEND` and `REPLACE` are supported. - -* To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. - -* The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. + -For session-based rules, create dedicated user accounts for your application users and apply persisted rules. This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. -==== === Indexing Several features within ThoughtSpot, such as autocompletion in Search on values within columns, use ThoughtSpot indexing. Due to the runtime nature of ABAC tokens, ThoughtSpot indexing will not be restricted by the values supplied in a token. This means the indexed columns may expose values in search suggestions or autocompletion that a user should not see, even if ABAC filters would block access to the underlying data. To prevent this, you can do one of the following: @@ -58,8 +50,6 @@ Several features within ThoughtSpot, such as autocompletion in Search on values * Define an RLS rule on those fields, since RLS is enforced at the indexing layer and will secure suggestions and sample values. - - == Create formula variables To view the variables available on your instance, use the `POST /api/rest/2.0/template/variables/search` API call. To create a new variable, use the `/api/rest/2.0/template/variables/create` API endpoint. @@ -388,9 +378,22 @@ To retrieve user information and object properties, you can use the `POST /api/r You can also use the `POST /api/rest/2.0/template/variables/search` API call to xref:variables.adoc#_get_variables[get the list of variables] assigned to a specific user, Org, and Model. -==== Updating variable values for a user +=== Resetting a User or a Variable +[NOTE] +==== + +* In the legacy JWT ABAC token requests, you could set the `persist_option` parameter to `NONE`, `APPEND`, `REPLACE`, and `RESET`. However, when you use the `variable_values` parameter in the JWT API token request to set up RLS values for a given user, only `APPEND` and `REPLACE` are supported. + +* To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. + +* The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. + +For session-based rules, create dedicated user accounts for your application users and apply persisted rules. This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. +==== + +=== Updating variable values for a user To update variable values for a user, you can use the `/api/rest/2.0/template/variables/update-values` endpoint, or `/api/rest/2.0/auth/token/custom` endpoint when logging in the user. Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it does not support updating variable values. + == Verify the entitlements To verify the entitlements: From 7e7d382b6f7171d5d28ff8dc513e8e218acca744 Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:17:51 -0600 Subject: [PATCH 32/44] Update abac_rls-variables.adoc moved RLS Rule examples into one section and added an intro, placed TS_WILDCARD_ALL in this intro --- modules/ROOT/pages/abac_rls-variables.adoc | 63 ++++++++++++---------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index cc0abc91f..6bd42228d 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -35,13 +35,11 @@ The ABAC implementation with formula variables and RLS rules includes the follow * xref:abac_rls-variables.adoc#_create_formula_variables[Creating formula variables] + To generate tokens with variable attributes, the variables must be available in ThoughtSpot. To create variables, use the xref:variables.adoc#_create_a_variable[Variable REST API]. * xref:abac_rls-variables.adoc#_add_rls_rules_with_variable_references[Adding RLS rules with formula variables] + -When defining an RLS rule with variables, use the `ts_var` function. These RLS rules will apply to the Models, Liveboards, and other objects derived from that Table. +When defining an RLS rule with variables, use the `ts_var` function. These RLS rules will apply to the Models, Liveboards, and other objects derived from that Table. *A formula variable must be defined before it can be used in an RLS rule.* * xref:abac_rls-variables.adoc#_define_values_and_scope_for_variables[Creating a token request with variable attributes] + -To generate a JWT token, use the `/api/rest/2.0/auth/token/custom` REST API endpoint. The token generation request must include the variable attributes, which will be used for RLS evaluation to enable per‑user entitlements and data filters across all the objects derived from the Table. +Attribute values are assigned to users by requesting a token using the `/api/rest/2.0/auth/token/custom` REST API endpoint. * xref:abac_rls-variables.adoc#_verify_the_entitlements[Verifying entitlements] + -To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. Variables and their values are visible to administrators using the `/api/rest/2.0/template/variables/search` endpoint with the `response_content` parameter set to `METADATA_AND_VALUES` - - +To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. Variables and their assigned values are visible to administrators using the `/api/rest/2.0/template/variables/search` endpoint with the `response_content` parameter set to `METADATA_AND_VALUES` === Indexing Several features within ThoughtSpot, such as autocompletion in Search on values within columns, use ThoughtSpot indexing. Due to the runtime nature of ABAC tokens, ThoughtSpot indexing will not be restricted by the values supplied in a token. This means the indexed columns may expose values in search suggestions or autocompletion that a user should not see, even if ABAC filters would block access to the underlying data. To prevent this, you can do one of the following: @@ -51,8 +49,11 @@ Several features within ThoughtSpot, such as autocompletion in Search on values == Create formula variables +Formula variables must be defined in ThoughtSpot before they can be used in any RLS rule via the `ts_var()` formula. -To view the variables available on your instance, use the `POST /api/rest/2.0/template/variables/search` API call. To create a new variable, use the `/api/rest/2.0/template/variables/create` API endpoint. +To view the variables available on your instance, use the `POST /api/rest/2.0/template/variables/search` API call. + +To create a new variable, use the `/api/rest/2.0/template/variables/create` API endpoint. To configure formula variables for all Orgs on your instance or the Primary Org, cluster administration privileges are required. Org administrators can configure formula variables for their respective Orgs. Users with the `CAN_MANAGE_VARIABLES` (*Can manage variables*) role privilege can also create and manage variables for their Org context. @@ -69,33 +70,46 @@ During variable creation, specify the xref:variables.adoc#data_type[`data_type`] Formula variables for `BOOLEAN` and `TIME` data types are not supported. -[source,cURL] +[source,JSON] ---- -curl -X POST \ - --url 'https://{ThoughtSpot-Host}/]api/rest/2.0/template/variables/create' \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer {AUTH_TOKEN}' \ - --data-raw '{ +{ "type": "FORMULA_VARIABLE", "name": "country_var", "is_sensitive": false, "data_type": "VARCHAR" -}' +} ---- -The variable update API allows assigning variable values and setting the scope. In the ABAC implementation, administrators can set the variable values and scope when xref:abac_rls-variables.adoc#_create_an_abac_token_request_with_variable_attributes[generating a JWT] using the `/api/rest/2.0/auth/token/custom` API endpoint. +The variable update API (`/api/rest/2.0/template/variables/{identifier}/update`) allows for updating the variable name and other attrbutes of the variable definition, but not the values assigned to users or other principals. -== Add RLS rules with variable references -To define RLS rules with variables for a Table: +Variable values are either set through for a user xref:abac_rls-variables.adoc#_create_an_abac_token_request_with_variable_attributes[generating a token] using the `/api/rest/2.0/auth/token/custom` API endpoint or via the Update Variable Values REST API. + +== Add or update RLS rules with variable references +RLS rules are defined on Table objects: . Navigate to the Data workspace and click the Table for which to define RLS rules. . Click *Row security* and then click *+ Add row security*. -. In the *Row Security Editor*, define the rules. To reference the formula variable in the rule, use the `ts_var` function. For example, If you want to limit user access to data of a specific region, you can create a region-specific variable, `region = ts_var(region_var)`, and assign values in the token request. +. In the *Row Security Editor*, define the rules. To reference the formula variable in the rule, use the `ts_var(varName)` function, with no quotes around the formula variable name. For example, to limit a column called `region` to the values set in a formula varible called `region_var`, set the RLS rule to: `region = ts_var(region_var)`. +[NOTE] +==== +Variable values are set through the token request. The RLS rule specifies how the values will be used in the generated RLS WHERE clauses in the SQL. +==== + +=== RLS Rule Examples +RLS rules must always evaluate logically to SQL boolean `TRUE` or `FALSE`. + +If a user has no variable values for a given formula variable, this will result in `FALSE`. + +There is a special wildcard value `['TS_WILDCARD_ALL']` that a formula variable can be set to represent 'Allow All'. -=== RLS rule with a single variable reference +In this example, `customer` represents the column `customer` in the table and `customer_var` represents variable. If the value of the `customer_var` variable is set to `TS_WILDCARD_ALL`, the user can access all customers in the column. +---- +customer = ts_var(customer_var) +---- + +==== RLS rule with a single variable reference In this formula example, `country` refers to the "country" column in the data table, and `country_var` is the variable. ---- @@ -106,7 +120,7 @@ If `country_var` is assigned a single value, the user is permitted to view only If `country_var` is assigned multiple values, the formula translates to `country IN ('value1', 'value2', ...)`. The query engine interprets `=` as the `IN` clause in this case and returns rows that match these values; for example, `WHERE country IN ('Australia', 'Germany')`. -=== RLS rules with multiple variables +==== RLS rules with multiple variables The RLS rules support the `AND` operator, which means that you can combine multiple conditions in a single RLS rule, so that a row is accessible only if all the specified conditions are met. The following rule restricts data access to rows if the `country` column in the data table matches the value assigned to `country_var` and the `Department` column matches the value assigned to `department_var` for that user. @@ -121,14 +135,7 @@ The rule in this example restricts data access to rows where the `region` column region = ts_var(region_var) AND product = ts_var(product_var) ---- -=== Allow all rule with a TS_WILDCARD_ALL variable -In this example, `customer` represents the column `customer` in the table and `customer_var` represents variable. If the value of the customer_var variable is set to `TS_WILDCARD_ALL`, the user can access all customers in the column. - ----- -customer = ts_var(customer_var) ----- - -=== Group override rule with variable-based check +==== Group override rule with variable-based check In any security formula you build, you may want a clause that gives access to all data to certain groups. In the rule definition, you can include system variables, such as `ts_groups`, to build your preferred logic: In this example, users can access data if they are in the "data developers" group, or if the `Department` column matches the value assigned to their `department_var` variable. From 1107b756b81c0afb0626577e8f8cf6b83016d681 Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:31:45 -0600 Subject: [PATCH 33/44] Update abac_rls-variables.adoc Continued simplification and reordering --- modules/ROOT/pages/abac_rls-variables.adoc | 72 +++++++++------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 6bd42228d..3b59fc125 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -160,7 +160,9 @@ The following rule restricts access to rows where the `date_column` is within th == Create an ABAC token request with variable attributes -To generate a token with variable attributes, use the `POST /api/rest/2.0/auth/token/custom` API call. +To set or update variable values for a user, use the POST `/api/rest/2.0/auth/token/custom` endpoint when logging in the user. + +You can also use the `/api/rest/2.0/template/variables/update-values` endpoint for bulk operations or targeted resets. The variable attributes defined in the token request take effect only if they are referenced in an RLS rule. If the variables are not used in any formula or RLS rule, they will have no impact on data access. Before generating the request with variable attributes, ensure that the xref:abac_rls-variables.adoc#_add_rls_rules_with_formula_variables_to_tables[variables are added to the RLS rules] for the Table. @@ -196,15 +198,19 @@ __Array of strings or numeric values__. When assigning values, ensure the data f All values are passed into the token as *arrays of strings*, even if the column is a numeric or date type in ThoughtSpot and the database. The column data type will be respected in the query issued to the database. -==== Allow all values by default +==== Allow all wildcard value -To allow all values by default, specify `["TS_WILDCARD_ALL"]` as the variable value to grant access to all values in a given column. +To allow all values for a given field, set the formula variable value to an array using the wildcard: `["TS_WILDCARD_ALL"]`. -In this example, the user is allowed all access for one variable, while for the others, specific values are set. +In this example, the user is allowed all access for one variable, while for the other, specific values are set. [source,JSON] ---- "variable_values": [ + { + "name": "product_var", + "values": ["TS_WILDCARD_ALL"] + } { "name": "country_var", "values": [ @@ -212,19 +218,6 @@ In this example, the user is allowed all access for one variable, while for the "Singapore", "Australia" ] - }, - { - "name": "department_var", - "values": [ - "Sales", - "Marketing" - ] - }, - { - "name": "product_var", - "values": [ - "TS_WILDCARD_ALL" - ] } ] ---- @@ -299,13 +292,9 @@ To apply variable entitlements to a user session, you must ensure that the RLS r The following example shows the request body for generating a token with formula variable attributes: -[source,cURL] +[source,JSON] ---- - curl -X POST \ - --url 'https://{ThoughtSpot-Host}/api/rest/2.0/auth/token/custom' \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - --data-raw '{ +{ "username": "UserA", "validity_time_in_sec": 300, "persist_option": "APPEND", @@ -313,24 +302,17 @@ The following example shows the request body for generating a token with formula "secret_key": "f8aa445b-5ff1-4a35-a58f-e324133320d5", "variable_values": [ { - "name": "country_var", - "values": [ - "Japan", - "Singapore", - "Australia" - ] - }, - { - "name": "department_var", + "name": "product_var", "values": [ - "Sales", - "Marketing" + "TS_WILDCARD_ALL" ] }, { - "name": "product_var", + "name": "country_var", "values": [ - "TS_WILDCARD_ALL" + "Japan", + "Singapore", + "Australia" ] } ], @@ -340,14 +322,14 @@ The following example shows the request body for generating a token with formula "identifier": "35aa85fe-fbb4-4862-a335-f69679ebb6e0" } ] -}' +} ---- -If the request is successful, ThoughtSpot generates a token and sends the token details in the API response. +If the request is successful, ThoughtSpot generates a token and sends the token details in the API response. [NOTE] ==== -ABAC details are sent in a JWT that can be used as a bearer token for cookieless trusted authentication, REST API calls, or as a sign-in token to start a session. JWTs are compressed by default to handle large payloads. It is recommended to keep the compression enabled to ensure all JWT tokens can get properly interpreted by the application regardless of their size, and to obfuscate the values passed in the JWT payload. If you want to disable it, contact ThoughtSpot Support. +ThoughtSpot access tokens are JWTs that are compressed by default to handle large payloads. It is recommended to keep the compression enabled to ensure all tokens can get properly interpreted by the application regardless of their size, and to obfuscate the values passed in the payload. If you want to disable it, contact ThoughtSpot Support. ==== === Verify the variable assignment @@ -385,7 +367,13 @@ To retrieve user information and object properties, you can use the `POST /api/r You can also use the `POST /api/rest/2.0/template/variables/search` API call to xref:variables.adoc#_get_variables[get the list of variables] assigned to a specific user, Org, and Model. -=== Resetting a User or a Variable +== Resetting a User or a Variable + +[NOTE] +==== +Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it does not support updating variable values. +==== + [NOTE] ==== @@ -397,10 +385,6 @@ You can also use the `POST /api/rest/2.0/template/variables/search` API call to For session-based rules, create dedicated user accounts for your application users and apply persisted rules. This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. ==== -=== Updating variable values for a user -To update variable values for a user, you can use the `/api/rest/2.0/template/variables/update-values` endpoint, or `/api/rest/2.0/auth/token/custom` endpoint when logging in the user. Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it does not support updating variable values. - - == Verify the entitlements To verify the entitlements: From cb385640713c9251278784f6128de9b2fddf1567 Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:17:33 -0600 Subject: [PATCH 34/44] Update abac_rls-variables.adoc Closer to finished, need to add the RESET and empty array mechanisms --- modules/ROOT/pages/abac_rls-variables.adoc | 63 ++++++++++++---------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 3b59fc125..7ca6c1d94 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -238,23 +238,45 @@ Due to this error, no data is returned, effectively denying all data access for === Persist options and session-based rules -Variable attributes must be *persisted* for them to apply to user sessions when using xref:trusted-authentication.adoc#cookie[cookie-based trusted authentication] or scheduled reports. To specify whether variable attributes and rules should persist for user sessions, you must define the `persist_option` parameter. +Variable attributes must be *persisted* for them to apply. -To append or replace the attributes, use the following options: +To append or replace the attribute values for a user, use one of the following `persist_options` in the token request: + +* `REPLACE` + +Replaces the full set of existing variable assignments with the new values from the token request. * `APPEND` + -Adds the attributes defined in the API request to the user properties. These properties will be applied to the current and future user sessions and scheduled reports until they are explicitly changed through a token update request. +Adds the attribute values defined in the API request to the existing attribute values for the user. -* `REPLACE` + -Replaces existing variable assignments with the new values. +If you don't want to append or replace any attribute values, do not pass any details about the variable in the token update request. + +[NOTE] +==== +* The ABAC implementation with RLS and formula variables does not support session-based rules. Do not use the legacy `persist_option` value of `NONE`. +* `"persist_option": "RESET"` attribute is also a legacy value and is not supported. +==== + +=== Resetting a User or a Variable +Passing an empty array along with a formula variable name in the token request *does not reset the attribute values* for that formula variable for that user. + +To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. [NOTE] ==== -* The ABAC implementation with RLS and formula variables does not support session-based rules. Therefore, ThoughtSpot does not recommend setting the `persist_option` attribute to `NONE`. -* If you don't want to append or replace any attributes, do not pass any variable values in the token update request. -* Resetting attributes using the`"persist_option": "RESET"` attribute in the token request is not supported. Passing an empty array does not reset the attributes. To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. +Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it does not support updating variable values. ==== +=== Session-based ABAC and one-time users + +The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. + +For session-based rules, create dedicated one-time user accounts for your application users and apply persisted rules. Unless specifically stated in your contract, there is no limit to the number of users that can be created and provisioned in ThoughtSpot. + +This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. + +You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. + + === Variable scope To restrict the scope of the variable attributes and rules to a specific Org context and object, define the `org_identifier` and `objects`. @@ -332,7 +354,10 @@ If the request is successful, ThoughtSpot generates a token and sends the token ThoughtSpot access tokens are JWTs that are compressed by default to handle large payloads. It is recommended to keep the compression enabled to ensure all tokens can get properly interpreted by the application regardless of their size, and to obfuscate the values passed in the payload. If you want to disable it, contact ThoughtSpot Support. ==== +//// +--- THIS IS NOT TRUE ANYMORE === Verify the variable assignment + To retrieve user information and object properties, you can use the `POST /api/rest/2.0/users/search` API call. To include variable details in the response, set the `include_variable_values` parameter to `true` in the API request body. This allows you to fetch variable values associated with the user in the specified context. [source,JSON] @@ -364,28 +389,12 @@ To retrieve user information and object properties, you can use the `POST /api/r } } ---- +//// -You can also use the `POST /api/rest/2.0/template/variables/search` API call to xref:variables.adoc#_get_variables[get the list of variables] assigned to a specific user, Org, and Model. - -== Resetting a User or a Variable - -[NOTE] -==== -Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it does not support updating variable values. -==== - -[NOTE] -==== - -* In the legacy JWT ABAC token requests, you could set the `persist_option` parameter to `NONE`, `APPEND`, `REPLACE`, and `RESET`. However, when you use the `variable_values` parameter in the JWT API token request to set up RLS values for a given user, only `APPEND` and `REPLACE` are supported. - -* To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. - -* The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. + -For session-based rules, create dedicated user accounts for your application users and apply persisted rules. This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. -==== == Verify the entitlements +You can also use the `POST /api/rest/2.0/template/variables/search` API call to xref:variables.adoc#_get_variables[get the list of variables] assigned to a specific user, Org, and Model. + To verify the entitlements: . Log in to your app with a user account that does not have the *Can administer and bypass RLS* privilege, and initiate the user session with the ABAC token. From d2c169121a3774b650dbf55cf9b0c88cd724b47a Mon Sep 17 00:00:00 2001 From: Bryant Howell - ThoughtSpot <83678239+bryanthowell-ts@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:40:02 -0600 Subject: [PATCH 35/44] Update abac_rls-variables.adoc Added requests and responses on teh variable update APIs --- modules/ROOT/pages/abac_rls-variables.adoc | 95 +++++++++++++++++++--- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 7ca6c1d94..892f41276 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -259,7 +259,36 @@ If you don't want to append or replace any attribute values, do not pass any det === Resetting a User or a Variable Passing an empty array along with a formula variable name in the token request *does not reset the attribute values* for that formula variable for that user. -To reset the formula variable attributes of a user, use the `/api/rest/2.0/template/variables/update-values` API endpoint. +To change the formula variable attributes of a user for a particular, particularly to set their entitlements to an empty set, use the `/api/rest/2.0/template/variables/update-values` API endpoint. + +[WARNING] +==== +The `RESET` operation erases all variable value settings for all users for a variable, regardless of any `variable_value_scope` provided. Use with caution, it is a complete reset. +==== + +A formula variable exists across all Orgs in ThoughtSpot, but the values are recorded on a per Org and per Principal basis. To use the Update Variable Values REST API, you'll need to provide the `org_identifier` as well as the username as `principal_identifier` and set `principal_type` to `USER` as seen below: + +[,json] +---- +{ + "variable_assignment": [ + { + "variable_identifier": "country_var", + "variable_values": [], + "operation": "REPLACE" + } + ], + "variable_value_scope": [ + { + "org_identifier": "Prod", + "principal_type": "USER", + "principal_identifier": "jane.smith@company.com" + } + ] +} +---- +The above command would result in jane.smith@company.com being denied any access when `country_var` is used in an RLS rule. + [NOTE] ==== @@ -302,17 +331,7 @@ The API supports only the `LOGICAL_TABLE` object type. If the object ID is not specified in the API request, the variable values will be applied to all formulas and rules that use those variables, across all objects in the Org for that user. -==== Apply to Org context - -The `org_identifier` attribute in the token request specifies the Org context for the user session and entitlements. - -If the `org_identifier` parameter is not defined in the token request, the token is issued for the user's last logged-in Org. For new users, the token will be assigned to the default Org on their instance. - -To apply variable entitlements to a user session, you must ensure that the RLS rules with variables and relevant objects are available in the Org context specified in the token request. - -=== Example request body - -The following example shows the request body for generating a token with formula variable attributes: +The following example shows the request body for generating a token with formula variable attributes scoped to a particular Model object: [source,JSON] ---- @@ -347,13 +366,20 @@ The following example shows the request body for generating a token with formula } ---- -If the request is successful, ThoughtSpot generates a token and sends the token details in the API response. +==== Apply to Org context + +The `org_identifier` attribute in the token request specifies the Org context for the user session and entitlements. + +If the `org_identifier` parameter is not defined in the token request, the token is issued for the user's last logged-in Org. For new users, the token will be assigned to the default Org on their instance. + +To apply variable entitlements to a user session, you must ensure that the RLS rules with variables and relevant objects are available in the Org context specified in the token request. [NOTE] ==== ThoughtSpot access tokens are JWTs that are compressed by default to handle large payloads. It is recommended to keep the compression enabled to ensure all tokens can get properly interpreted by the application regardless of their size, and to obfuscate the values passed in the payload. If you want to disable it, contact ThoughtSpot Support. ==== + //// --- THIS IS NOT TRUE ANYMORE === Verify the variable assignment @@ -395,6 +421,49 @@ To retrieve user information and object properties, you can use the `POST /api/r == Verify the entitlements You can also use the `POST /api/rest/2.0/template/variables/search` API call to xref:variables.adoc#_get_variables[get the list of variables] assigned to a specific user, Org, and Model. +Set the `response_content` parameter to `METADATA_AND_VALUES` to see the values that have been set for each user per Org: + +[,json] +---- +{ + "record_offset": 0, + "record_size": -1, + "response_content": "METADATA_AND_VALUES" +} +---- + +Would result in the response: + +[,json] +---- +[ + { "id":"d3abc655-b706-4f91-90ea-cc26bc966d46", + "name":"country_var", + "variable_type":"FORMULA_VARIABLE", + "sensitive":false, + "values":[ + { + "value": null, + "value_list": ["CustomerC","CustomerD"], + "org_identifier": "Prod", + "principal_type": "USER", + "principal_identifier": "ron.smith@company.com", + "model_identifier": null, + "priority": null + },{ + "value": null, + "value_list": null, + "org_identifier": "Prod", + "principal_type": "USER", + "principal_identifier": "jane.smith@company.com", + "model_identifier": null, + "priority": null + } + ] + } +] +---- + To verify the entitlements: . Log in to your app with a user account that does not have the *Can administer and bypass RLS* privilege, and initiate the user session with the ABAC token. From 80405d7e5387a11ef7ff612ddafd816057084a97 Mon Sep 17 00:00:00 2001 From: ShashiSubramanya Date: Fri, 27 Feb 2026 23:57:58 +0530 Subject: [PATCH 36/44] minor edits and typo fixes --- modules/ROOT/pages/abac_rls-variables.adoc | 81 +++++++++++----------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/modules/ROOT/pages/abac_rls-variables.adoc b/modules/ROOT/pages/abac_rls-variables.adoc index 892f41276..7d3cc95f5 100644 --- a/modules/ROOT/pages/abac_rls-variables.adoc +++ b/modules/ROOT/pages/abac_rls-variables.adoc @@ -2,15 +2,15 @@ :toc: true :toclevels: 2 -:page-title: ABAC via tokens +:page-title: ABAC via RLS with variables :page-pageid: abac-via-rls-variables :page-description: Attribute-based access control pattern with variable attributes referenced in Row-Level Security (RLS) rules on a given table. -Attribute-Based Access Control (ABAC) is an access control model in which security entitlements are assigned to a ThoughtSpot user directly as a set of attributes with lists of values, rather than relying on a JOINed entitlements table within the data model. +Attribute-Based Access Control (ABAC) is an access control model in which security entitlements are assigned to a ThoughtSpot user directly as a set of attributes with lists of values, rather than relying on a JOINed entitlements table within the data model. -ThoughtSpot allows assigning attribute values to a user at session creation time by adding the values to the user's access token request. +ThoughtSpot allows assigning attribute values to a user at session creation time by adding the values to the user's access token request. -RLS Rules are defined on the table objects, which binds dynamically generated `WHERE` clauses to any generated query. Within the RLS Rules, the attributes are referenced by their variable names using the `ts_var()` function to dynamically filter data and enable user-specific security policies. +RLS rules are defined on table objects, which bind dynamically generated `WHERE` clauses to any generated query. Within RLS rules, attributes are referenced by their variable names using the `ts_var()` function to dynamically filter data and enable user-specific security policies. == Overview @@ -34,12 +34,12 @@ The ABAC implementation with formula variables and RLS rules includes the follow * xref:abac_rls-variables.adoc#_create_formula_variables[Creating formula variables] + To generate tokens with variable attributes, the variables must be available in ThoughtSpot. To create variables, use the xref:variables.adoc#_create_a_variable[Variable REST API]. -* xref:abac_rls-variables.adoc#_add_rls_rules_with_variable_references[Adding RLS rules with formula variables] + +* xref:abac_rls-variables.adoc#_add_or_update_rls_rules_with_variable_references[Adding RLS rules with formula variables] + When defining an RLS rule with variables, use the `ts_var` function. These RLS rules will apply to the Models, Liveboards, and other objects derived from that Table. *A formula variable must be defined before it can be used in an RLS rule.* -* xref:abac_rls-variables.adoc#_define_values_and_scope_for_variables[Creating a token request with variable attributes] + -Attribute values are assigned to users by requesting a token using the `/api/rest/2.0/auth/token/custom` REST API endpoint. +* xref:abac_rls-variables.adoc#_create_an_abac_token_request_with_variable_attributes[Creating a token request with variable attributes] + +Attribute values are assigned to users by requesting a token using the `/api/rest/2.0/auth/token/custom` REST API endpoint. * xref:abac_rls-variables.adoc#_verify_the_entitlements[Verifying entitlements] + -To ensure data security rules are applied, check user entitlements and verify if they are translated accurately during query generation. Variables and their assigned values are visible to administrators using the `/api/rest/2.0/template/variables/search` endpoint with the `response_content` parameter set to `METADATA_AND_VALUES` +To ensure data security rules are applied, check user entitlements and verify that they are translated accurately during query generation. Variables and their assigned values are visible to administrators using the `/api/rest/2.0/template/variables/search` endpoint with the `response_content` parameter set to `METADATA_AND_VALUES`. === Indexing Several features within ThoughtSpot, such as autocompletion in Search on values within columns, use ThoughtSpot indexing. Due to the runtime nature of ABAC tokens, ThoughtSpot indexing will not be restricted by the values supplied in a token. This means the indexed columns may expose values in search suggestions or autocompletion that a user should not see, even if ABAC filters would block access to the underlying data. To prevent this, you can do one of the following: @@ -51,7 +51,7 @@ Several features within ThoughtSpot, such as autocompletion in Search on values == Create formula variables Formula variables must be defined in ThoughtSpot before they can be used in any RLS rule via the `ts_var()` formula. -To view the variables available on your instance, use the `POST /api/rest/2.0/template/variables/search` API call. +To view the variables available on your instance, use the `POST /api/rest/2.0/template/variables/search` API call. To create a new variable, use the `/api/rest/2.0/template/variables/create` API endpoint. @@ -80,30 +80,30 @@ Formula variables for `BOOLEAN` and `TIME` data types are not supported. } ---- -The variable update API (`/api/rest/2.0/template/variables/{identifier}/update`) allows for updating the variable name and other attrbutes of the variable definition, but not the values assigned to users or other principals. +The variable update API (`/api/rest/2.0/template/variables/{identifier}/update`) allows updating the variable name and other attributes of the variable definition, but not the values assigned to users or other principals. -Variable values are either set through for a user xref:abac_rls-variables.adoc#_create_an_abac_token_request_with_variable_attributes[generating a token] using the `/api/rest/2.0/auth/token/custom` API endpoint or via the Update Variable Values REST API. +Variable values are set either by xref:abac_rls-variables.adoc#_create_an_abac_token_request_with_variable_attributes[generating a token] using the `/api/rest/2.0/auth/token/custom` API endpoint or via the Update Variable Values REST API. == Add or update RLS rules with variable references RLS rules are defined on Table objects: . Navigate to the Data workspace and click the Table for which to define RLS rules. . Click *Row security* and then click *+ Add row security*. -. In the *Row Security Editor*, define the rules. To reference the formula variable in the rule, use the `ts_var(varName)` function, with no quotes around the formula variable name. For example, to limit a column called `region` to the values set in a formula varible called `region_var`, set the RLS rule to: `region = ts_var(region_var)`. +. In the *Row Security Editor*, define the rules. To reference the formula variable in the rule, use the `ts_var(varName)` function, with no quotes around the formula variable name. For example, to limit a column called `region` to the values set in a formula variable called `region_var`, set the RLS rule to: `region = ts_var(region_var)`. [NOTE] ==== Variable values are set through the token request. The RLS rule specifies how the values will be used in the generated RLS WHERE clauses in the SQL. ==== -=== RLS Rule Examples +=== RLS rule examples RLS rules must always evaluate logically to SQL boolean `TRUE` or `FALSE`. -If a user has no variable values for a given formula variable, this will result in `FALSE`. +If a user has no variable values for a given formula variable, this results in `FALSE`. -There is a special wildcard value `['TS_WILDCARD_ALL']` that a formula variable can be set to represent 'Allow All'. +There is a special wildcard value `['TS_WILDCARD_ALL']` that a formula variable can be set to represent "Allow all". -In this example, `customer` represents the column `customer` in the table and `customer_var` represents variable. If the value of the `customer_var` variable is set to `TS_WILDCARD_ALL`, the user can access all customers in the column. +In this example, `customer` represents the `customer` column in the table, and `customer_var` represents a variable. If the value of `customer_var` is set to `TS_WILDCARD_ALL`, the user can access all customers in the column. ---- customer = ts_var(customer_var) @@ -146,7 +146,7 @@ In this example, users can access data if they are in the "data developers" grou ==== Variables with numeric and Date data types -The following rule enforces a numeric threshold and restricts access to rows where the Revenue value is less than or equal to the value provided by the `revenue_cap_var` variable. +The following rule enforces a numeric threshold and restricts access to rows where the Revenue value is less than or equal to the value provided by the `revenue_cap_var` variable. ---- Revenue <= to_double(ts_var(revenue_cap_var)) @@ -160,11 +160,11 @@ The following rule restricts access to rows where the `date_column` is within th == Create an ABAC token request with variable attributes -To set or update variable values for a user, use the POST `/api/rest/2.0/auth/token/custom` endpoint when logging in the user. +To set or update variable values for a user, use the `POST /api/rest/2.0/auth/token/custom` endpoint when logging in the user. -You can also use the `/api/rest/2.0/template/variables/update-values` endpoint for bulk operations or targeted resets. +You can also use the `/api/rest/2.0/template/variables/update-values` endpoint for bulk operations or targeted resets. -The variable attributes defined in the token request take effect only if they are referenced in an RLS rule. If the variables are not used in any formula or RLS rule, they will have no impact on data access. Before generating the request with variable attributes, ensure that the xref:abac_rls-variables.adoc#_add_rls_rules_with_formula_variables_to_tables[variables are added to the RLS rules] for the Table. +The variable attributes defined in the token request take effect only if they are referenced in an RLS rule. If the variables are not used in any formula or RLS rule, they have no impact on data access. Before generating the request with variable attributes, ensure that the xref:abac_rls-variables.adoc#_add_or_update_rls_rules_with_variable_references[variables are added to the RLS rules] for the table. In the token request, include the following properties along with the `username`, xref:trusted-auth-secret-key.adoc[`secret_key`]: @@ -180,7 +180,7 @@ The `variable_values` array requires the following parameters: * `name` + __String__. Name of the formula variable. The formula variable referenced on the token request must be available in ThoughtSpot and included in the RLS rule. * `values` + -__Array of strings or numeric values__. When assigning values, ensure the data format of values matches the data type set of that variable. If you are adding a variable to filter by country with the `VARCHAR` data type, specify the string values as shown in this example: +__Array of strings or numeric values__. When assigning values, ensure the data format of the values matches the data type set for that variable. If you are adding a variable to filter by country with the `VARCHAR` data type, specify string values as shown in this example: [source,JSON] ---- @@ -210,7 +210,7 @@ In this example, the user is allowed all access for one variable, while for the { "name": "product_var", "values": ["TS_WILDCARD_ALL"] - } + }, { "name": "country_var", "values": [ @@ -225,10 +225,13 @@ In this example, the user is allowed all access for one variable, while for the If `TS_WILDCARD_ALL` is set for variable attributes, ensure that the RLS rules are defined clearly on the Table to avoid unintended data exposure. ==== Deny all by default -For every variable included in the token request, you can assign one or more values. All variables referenced in RLS rules are required. If a variable is not assigned any value, query generation will fail with the following error. +For every variable included in the token request, you can assign one or more values. All variables referenced in RLS rules are required. If a variable is not assigned any value, query generation fails with the following error: -`Error in loading data -No values are assigned to some or all Formula Variables` +[source,text] +---- +Error in loading data +No values are assigned to some or all Formula Variables +---- [.bordered] [.widthAuto] @@ -240,13 +243,13 @@ Due to this error, no data is returned, effectively denying all data access for Variable attributes must be *persisted* for them to apply. -To append or replace the attribute values for a user, use one of the following `persist_options` in the token request: +To append or replace attribute values for a user, use one of the following `persist_option` values in the token request: * `REPLACE` + Replaces the full set of existing variable assignments with the new values from the token request. -* `APPEND` + -Adds the attribute values defined in the API request to the existing attribute values for the user. +* `APPEND` + +Adds the attribute values defined in the API request to the existing attribute values for the user. If you don't want to append or replace any attribute values, do not pass any details about the variable in the token update request. @@ -256,14 +259,14 @@ If you don't want to append or replace any attribute values, do not pass any det * `"persist_option": "RESET"` attribute is also a legacy value and is not supported. ==== -=== Resetting a User or a Variable -Passing an empty array along with a formula variable name in the token request *does not reset the attribute values* for that formula variable for that user. +=== Resetting a user or a variable +Passing an empty array along with a formula variable name in the token request *does not reset the attribute values* for that formula variable for that user. -To change the formula variable attributes of a user for a particular, particularly to set their entitlements to an empty set, use the `/api/rest/2.0/template/variables/update-values` API endpoint. +To change formula variable attributes of a user, especially to set entitlements to an empty set, use the `/api/rest/2.0/template/variables/update-values` API endpoint. [WARNING] ==== -The `RESET` operation erases all variable value settings for all users for a variable, regardless of any `variable_value_scope` provided. Use with caution, it is a complete reset. +The `RESET` operation erases all variable value settings for all users for a variable, regardless of any `variable_value_scope` provided. Use with caution; it is a complete reset. ==== A formula variable exists across all Orgs in ThoughtSpot, but the values are recorded on a per Org and per Principal basis. To use the Update Variable Values REST API, you'll need to provide the `org_identifier` as well as the username as `principal_identifier` and set `principal_type` to `USER` as seen below: @@ -287,7 +290,7 @@ A formula variable exists across all Orgs in ThoughtSpot, but the values are rec ] } ---- -The above command would result in jane.smith@company.com being denied any access when `country_var` is used in an RLS rule. +The above command results in `jane.smith@company.com` being denied any access when `country_var` is used in an RLS rule. [NOTE] @@ -297,17 +300,17 @@ Do not use the `/api/rest/2.0/users/{user_identifier}/update` endpoint, as it do === Session-based ABAC and one-time users -The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. +The ability to set variable values only for the current session, previously achieved by setting `persist_option: NONE`, is not supported with RLS via ABAC. -For session-based rules, create dedicated one-time user accounts for your application users and apply persisted rules. Unless specifically stated in your contract, there is no limit to the number of users that can be created and provisioned in ThoughtSpot. +For session-based rules, create dedicated one-time user accounts for your application users and apply persisted rules. Unless specifically stated in your contract, there is no limit to the number of users that can be created and provisioned in ThoughtSpot. -This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. +This approach ensures that Liveboard schedule attachments enforce security rules and deliver only secured output to your end users. When combined with cookieless authentication, this configuration addresses all use cases that previously relied on session-based JWT. You can simplify user provisioning and programmatically manage user creation and deletion workflows using ThoughtSpot's REST APIs. === Variable scope -To restrict the scope of the variable attributes and rules to a specific Org context and object, define the `org_identifier` and `objects`. +To restrict the scope of variable attributes and rules to a specific Org context and object, define `org_identifier` and `objects`. ==== Apply to specific objects To apply variable entitlements to a specific object, specify the object IDs in the `objects` array as shown in this example: @@ -432,7 +435,7 @@ Set the `response_content` parameter to `METADATA_AND_VALUES` to see the values } ---- -Would result in the response: +This results in the following response: [,json] ---- @@ -476,4 +479,4 @@ To verify the entitlements: == Additional resources * For information about variables and variable APIs, see link:https://docs.thoughtspot.com/cloud/latest/rls-variables-reference[Variables] and xref:variables.adoc[Variable APIs]. -* For information about RLS rules,see xref:rls-rules.adoc[RLS Rules] and link:https://docs.thoughtspot.com/cloud/latest/security-rls[ThoughtSpot Product Documentation, window=_blank]. +* For information about RLS rules, see xref:rls-rules.adoc[RLS rules] and link:https://docs.thoughtspot.com/cloud/latest/security-rls[ThoughtSpot product documentation, window=_blank]. From bda89f23751c2badfe1e80d36a0586d1a1ddb6a7 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Mon, 2 Mar 2026 12:48:08 +0530 Subject: [PATCH 37/44] edits to sdk changelog --- modules/ROOT/pages/api-changelog.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/api-changelog.adoc b/modules/ROOT/pages/api-changelog.adoc index 883e802ca..923c15448 100644 --- a/modules/ROOT/pages/api-changelog.adoc +++ b/modules/ROOT/pages/api-changelog.adoc @@ -12,6 +12,10 @@ This changelog lists only the changes introduced in the Visual Embed SDK. For in [width="100%" cols="1,4"] |==== +|[tag redBackground]#DEPRECATED# a| **dataPanelV2** + +The `dataPanelV2` parameter is deprecated and can no longer be used to switch between the classic and new data panel experience. By default, the new data panel v2 experience is enabled on all ThoughtSpot embedded instances. + |[tag greenBackground]#NEW FEATURE# a| **Spotter experience** The SDK includes the following parameters, action IDs, and events to customize the Spotter embed experience. @@ -47,9 +51,6 @@ Is emitted when a saved chat is selected in the chat history sidebar. The Visual Embed SDK includes the `getCurrentContext()` function to fetch the current context and route host events to a specific xref:ContextType.adoc[context type] in the embedded view. For more information, see xref:events-context-aware-routing.adoc[Host events in multi-modal contexts]. -|[tag redBackground]#DEPRECATED# a| **dataPanelV2** - -The `dataPanelV2` parameter is deprecated and can no longer be used to switch between the classic and new data panel experience. By default, the new data panel v2 experience is enabled on all ThoughtSpot embedded instances. |[tag greenBackground]#NEW FEATURE# | `enableLinkOverridesV2` + From 40ec659e856677fc6232fe574274a9bb04bb41d4 Mon Sep 17 00:00:00 2001 From: Rani Gangwar Date: Thu, 5 Mar 2026 13:04:14 +0530 Subject: [PATCH 38/44] edited lb embed page --- modules/ROOT/pages/embed-pinboard copy.adoc | 570 ++++++++++++++++++++ static/doc-images/images/embed-lb.png | Bin 379767 -> 485365 bytes 2 files changed, 570 insertions(+) create mode 100644 modules/ROOT/pages/embed-pinboard copy.adoc diff --git a/modules/ROOT/pages/embed-pinboard copy.adoc b/modules/ROOT/pages/embed-pinboard copy.adoc new file mode 100644 index 000000000..3cb93fce3 --- /dev/null +++ b/modules/ROOT/pages/embed-pinboard copy.adoc @@ -0,0 +1,570 @@ += Embed a Liveboard +:toc: true +:toclevels: 2 + +:page-title: Embed Liveboards +:page-pageid: embed-liveboard +:page-description: You can use the LiveboardEmbed SDK library to embed a ThoughtSpot Liveboard in your app and use it for live insights + +This page explains how to embed a ThoughtSpot Liveboard in your Web page, portal, or application. + +A ThoughtSpot Liveboard is an interactive dashboard that presents a collection of visualizations pinned by a user. + +== Before you begin + +To embed a Liveboard, you need the following access and setup: + +* Access to a ThoughtSpot instance. +* Your host application domain is added to xref:security-settings.adoc[ThoughtSpot CSP and CORS allowlists]. +* Your application project has access to the xref:api-changelog.adoc[latest version of the Visual Embed SDK]. + + +== Import the LiveboardEmbed package + +Import the `LiveboardEmbed` SDK library to your application environment: + +**npm** +[source,JavaScript] +---- +import { + LiveboardEmbed, + AuthType, + init, + prefetch, + EmbedEvent +} +from '@thoughtspot/visual-embed-sdk'; +---- + +**ES6** +[source,JavaScript] +---- +