MCP App Store
by KeenEthics
Polar Analytics icon

Polar Analytics

By Polar Analytics, Inc.

Get New App Alerts

Overview

Polar is the trusted data layer for commerce brands. Connect your Shopify, Meta, Google, Klaviyo, and 45+ other data sources into a unified semantic layer with validated metrics, custom business rules, and cross-platform attribution. Turn Claude into your data analyst. Get answers on ROAS, CAC, top-performing SKUs, profit margins, and more, with answers grounded in consistent, reconciled data instead of raw API pulls. Polar eliminates the "different answer every time" problem by applying your business definitions, exclusions, and attribution logic to every query. Supports multi-store setups and works with major e-commerce integrations.

Tools

create_custom_dimension

ChatGPT
Create a new custom dimension for this tenant. A custom dimension is a rule-based (when/then/else) label derived from existing dimensions. Prefer reusing an existing custom dimension. Before calling this tool, inspect the custom_dimensions field returned by get_context and, if needed, fetch details via get_custom_dimension_details. Only create a new one if no existing dimension matches the user's intent semantically. ===== json payload shape (CustomDimension) ===== { "whenThen": [ { "when": [ WhenBlock, ... ], "then": [ "<label>" ] // array of bare strings }, ... ], "else": [ "<fallback label>" ] // array of bare strings } WhenBlock = { "lineOperator": "" | "and" | "or", // Trailing connector — joins this block to the NEXT one. // Last block always "". For multi-block whens, the first // and middle blocks carry "and"/"or"; only the final one is "". "leftValue": [ "dimension.<table>.<field>" ], // array, one entry "operator": "is" | "isNot" | "isInList" | "isNotInList" | "contains" | "notContains" | "containsCaseSensitive" | "notContainsCaseSensitive" | "startsWith" | "endsWith" | "lower" | "lowerOrEqual" | "greater" | "greaterOrEqual" | "isBefore" | "isAfter" | "isNull" | "isNotNull" | "isEmpty" | "isNotEmpty", "value": "<string>" // for isInList / isNotInList use a // comma-joined string: "US,CA,MX" } Required inputs: - conversation_id - title (short, human-readable name) - json (per the shape above) On success returns { id, title, editUrl }. The chat UI surfaces the editUrl as an "Open dimension editor" button on the tool-call card — do NOT repeat it as a markdown link in your visible response. Just tell the user the dimension was created and briefly describe what it groups.

create_custom_metric

ChatGPT
Create a new custom metric for this tenant. A custom metric is a formula built from a sequence of elements (existing metric refs, operators, values). Prefer reusing an existing metric. Call get_metrics first with custom-metrics included to see what already exists; only create if no existing metric matches the user's intent semantically. Aggregation note: the formula is evaluated per-row then SUMmed at the outer query level (ratio-of-SUM for simple arithmetic). For division on same-source metrics the aggregation becomes SUM-of-ratio, which can surprise the user on multi-day granularity. ===== metricData payload shape ===== Required: title, elements, formatting. description is optional. elements: array of typed-object nodes. Every element is an OBJECT, never a bare string. Valid shapes: - Metric reference: { "type": "metric", "value": "<table>.raw.<key>" // e.g. shopify_sales_main.raw.gross_sales | "<table>.computed.<key>" | "custom_<N>", "filters": [] // optional; omit or [] // when no filters apply } - Arithmetic operator: { "type": "operator", "value": "+" | "-" | "*" | "/" | "(" | ")" | "x" } - Literal value (numeric constant inside the formula): { "type": "value", "value": "<number-as-string>" } // e.g. "2" - Line break (visual only, rare): { "type": "linebreak", "value": "" } Filters on a metric element (optional). Each filter is one of: - OR element: { "dimension": "<dimKey>", "operator": "IS" | "ISNOT" | "CONTAINS" | "NOTCONTAINS", "values": ["a","b"] } - AND group: an array of the OR elements above Empty array or omitted field = "no filter on this metric ref". formatting: REQUIRED object, one of four discriminated shapes: - { "type": "number", "numberType": "integer" | "decimal", "prefix"?: string, "suffix"?: string, "metricRepresentation"?: "higher_is_better" | "lower_is_better" } - { "type": "percent", "metricRepresentation"?: "higher_is_better" | "lower_is_better" } - { "type": "currency", "currency"?: string, // 3-letter ISO "metricRepresentation"?: "higher_is_better" | "lower_is_better" } - { "type": "timedelta", "metricRepresentation"?: "higher_is_better" | "lower_is_better" } Required inputs: - conversation_id - metricData (per shape above) On success returns { id, title, editUrl }. The chat UI surfaces the editUrl as an "Open metric editor" button on the tool-call card — do NOT repeat it as a markdown link in your visible response. Just tell the user the metric was created and briefly describe what it computes.

create_dashboard

ChatGPT
Create a new dashboard in this workspace. A dashboard is a Polar page holding report blocks (tables, charts, metric cards), key-indicator sections and text blocks, optionally grouped into folders. Prefer reusing an existing dashboard. Call list_dashboards first and read the titles; only create a new dashboard when none of the existing ones covers the user's intent. Adding a block to the right dashboard beats creating a near-duplicate. Every block is an OBJECT with a "type". Valid shapes: - Existing report block (keep it as it is): { "type": "report", "id": <number> } - New report block (minted on save): { "type": "report", "json_value": { "title": string, "metrics": string[], "breakdowns"?: string[], "rules": object, "reportMode"?: string, "granularity"?: string } } json_value fields: - granularity: "day", "week", "month", "quarter", "year" or "none". Set it on every chart that shows a trend over time (line, stacked-line, 100-stacked-line, mixed, sparkline-cards, or bars over time): "day" by default, "week" or "month" when the user asks for it or the range spans many months. Without it the block follows the viewer's granularity picker, which defaults to "none" (Entire Range): one aggregate row, so a line chart draws a single point. Set "none" for totals and for charts broken down by a category rather than by date. - Existing key-indicator section: { "type": "kiSection", "id": <number> } - Text block: { "type": "text", "id"?: <number>, "value": "<markdown string>" } - Custom HTML block (a visual no report type can express). Before writing an "html" block, call get_html_block_guide — it documents window.polar, the row shape and worked examples. { "type": "html", "id"?: <number>, "title"?: string, "settings": { "html": "<div id='chart'></div><script>...</script>", "height"?: "auto" | <px> } } - Layout stack: { "type": "horizontal-stack" | "vertical-stack", "id"?: "<string>", "elements": [ ...nested blocks... ] } elements is the dashboard's WHOLE block list, in order, and it REPLACES whatever is on the dashboard today. When updating, pass back the id of every block you want to keep; omit id only for genuinely new blocks. This applies to every block type, text and stacks included: a block sent without its id gets a new one, which breaks anything that points at the old id. A report block sent without an id is also saved as a brand-new report, so an update that drops the ids re-creates every block and strands the originals as orphans. Call get_dashboard_details first to read the current blocks and their ids. Required inputs: - conversation_id - title - elements (pass [] for an empty dashboard) On success returns { id, title, deepLink, elements }, where elements is the saved block list in order, each with the type and id it was given — inline reports are minted server-side, so this is where their ids come from. Address them with update_report, reorder_dashboards or delete_report without a second get_dashboard_details call. Tell the user the dashboard was created and what it contains; the deep link opens it in Polar. Without folder the dashboard is a draft, kept out of the team folders until it is moved into one. Signed in with a personal login it is private to its creator; connected with a workspace API key (Slack, scripts) it is a team draft every workspace member sees under Drafts > Team drafts. The result's visibility says which; tell the user, and offer to file it.

create_document

ChatGPT
Create a document. A document is a title and a grid of elements, filed in folders next to dashboards. Share a written report as a document instead of an HTML report link. Elements: - elements is an array of report element objects, each { type, title, columnSpan, settings }. columnSpan (1 to 4) sets its width on a 4-column grid, 25% to 100%. Two elements with columnSpan 2 each sit side by side: use that for a short comparison, not by default. - Types: text (markdown in settings.value), keyindicator, table, html, and the chart types. - A text element is one small block, as in Notion: one heading, one paragraph or one list. A heading and the paragraph under it are two elements; a heading may share its element only with the single short paragraph directly under it, and only when the two never need to move apart. Tables, charts and key indicators are their own elements. - Send the whole document in one call: every element in elements, however many small blocks it has. - Editing: change only the element that changed. A new paragraph is a new text element at its position, not a rewrite of a larger element. - Elements are static: their numbers never refresh. State the date range they cover in a text element. - Every element is checked when the document is saved: an invalid one is refused with its index and the issues, and nothing is saved. Omit folder to create a draft, kept out of the team folders until it is moved into one with move_document. Required inputs: - conversation_id - title On success returns { id, title, visibility, folder, deepLink }.

create_file_upload_url

ChatGPT
Returns a short-lived, credential-free URL to upload ANY file (CSV, PDF, image, JSON, …) and the public link to share once it's uploaded. Use this to send a file the merchant can open. How it works (two steps — never put file contents into this call): 1. Call this tool with the file's name. It returns { upload_url, file_url }. 2. Upload the file's BYTES straight to upload_url with the baked helper: python3 /opt/hermes/polar-upload-file.py <local_path> "<upload_url>" then put file_url in your reply. (file_url is a CDN link the merchant can open; images unfurl inline in Slack.) Why not pass the file to a tool directly: tool arguments travel through the model's output, so a file encoded into a call is truncated and arrives corrupt. This path sends the bytes from disk straight to storage, so any size / type works.

create_folder

ChatGPT
Folders group dashboards in the Polar left-hand nav. A folder has a key, a title and an ordered list of dashboards. list_folders also returns the built-in "ki" and "report" nav sections and the orphan buckets under those string keys — they are not editable folders, so only numeric folder keys can be created, renamed, deleted or used as a move target. Creates a new folder. Prefer reusing an existing folder. Call list_folders first and only create one when no existing folder covers the same grouping. Required inputs: - conversation_id - title

create_label

ChatGPT
Labels are workspace-defined tags on custom metrics: { name, category, description, color }. Metrics may carry labels: workspace-defined tags { name, category, description } that the workspace's team attached on purpose (for example a "Certified" label under a "Governance" category). A label's description is authoritative guidance on when to use the metric: when several metrics could answer a question, prefer the one whose labels and label descriptions match the question, and mention the label when you cite the metric. Creates a new label. Names are unique per workspace (case-insensitive); call list_labels first and reuse an existing label when one already covers the same meaning. Write the description as guidance for whoever reads it next: what the label guarantees and when to prefer a metric that carries it (for example "Reviewed by finance, use instead of similarly named metrics"). Confirm the name and description with the user before creating unless they dictated them.

create_report

ChatGPT
Create a standalone report, belonging to no dashboard. A report is a saved Polar query block — a table, a chart or a set of metric cards — defined by the metrics it shows, the breakdowns it groups by and the filter rules it applies. A report is a first-class object: it exists in the workspace whether or not a dashboard displays it, and the same report can sit on several dashboards and schedules at once. The report definition is an object: - metrics: string[] — metric keys from get_metrics. At least one; an empty list is refused. - breakdowns: string[] — dimension keys to group by, from list_dimensions. The key "date" groups by time. - rules: object — filters keyed by dimension key, each value a list of { operator, value } clauses. Pass {} for no filter. - metricRules: object — same shape, applied per metric. - reportMode: one of "table", "bar", "line", "stacked-bar", "stacked-line", "100-stacked-bar", "100-stacked-line", "doughnut", "mixed", "metric-cards", "sparkline-cards". Defaults to "table". - granularity: "day", "week", "month", "quarter", "year" or "none". Set it on every chart that shows a trend over time (line, stacked-line, 100-stacked-line, mixed, sparkline-cards, or bars over time): "day" by default, "week" or "month" when the user asks for it or the range spans many months. Without it the block follows the viewer's granularity picker, which defaults to "none" (Entire Range): one aggregate row, so a line chart draws a single point. Set "none" for totals and for charts broken down by a category rather than by date. - top: { topLimit, direction, topMetric } — keeps the leading rows by one metric, which has to be one of metrics. - ordering: { columnKey, direction }, or a list of those. - dateRange: object — pins the report to its own dates instead of the dashboard's. Omit it to follow the dashboard. tab picks the kind, and only that tab's selection is needed: - { tab: "predefined", predefinedSelection }, where predefinedSelection is one of: "today", "thisWeek", "thisMonth", "thisYear", "yesterday", "lastWeek", "lastMonth", "lastYear", "all", "bfcm2019", "bfcm2020", "bfcm2021", "bfcm2022", "bfcm2023", "bfcm2024", "bfcm2025" Rolling windows such as the last 7 or 30 days are not predefined periods; use the relative tab. - { tab: "relative", relativeSelection: { count, granularity } }, where granularity is one of: "day", "week", "month", "quarter", "year", "whole_week", "whole_month", "whole_quarter", "whole_year" { count: 7, granularity: "day" } is the last 7 days; the whole_ variants count complete periods only. - { tab: "range", rangeSelection: { start, end } } — dates as "YYYY-MM-DD". - compare, displaySettings, colorSetup, targets — optional presentation settings, best carried over from get_report rather than invented. The usual way to add a NEW block to a dashboard is to pass the report inline inside elements on create_dashboard or update_dashboard, which mints it and attaches it in one call. This tool is for a report the user wants to keep on its own; attach it afterwards with move_element_to_dashboard. A definition with an empty metrics list is refused, and so is a top.topMetric that is not one of metrics. The refusal names the offending field, so read it and send a corrected definition. Required inputs: - conversation_id - title - report On success returns { id, title, deepLink }.

create_views_collection

ChatGPT
Create a views collection holding one or more views. A view is connectors + stores + rules: it limits every report to the chosen stores of each connector, each store optionally narrowed by rules on that connector's dimensions. Each view is { "title": string, "currency"?: string, "shared_rules"?: object, "connectors": [{ "connector_key": string, "stores"?: [{ "identifier": string, "rules"?: [{ "filter_key": string, "operator": string, "value"?: string or number or boolean }] }] }] } Take connector_key, store identifiers and filter_key values from list_views_collections; shared_rules keys are dimension keys from list_dimensions. Omit "stores" to keep every store of the connector. A store without rules keeps all of its rows. Polar Pixel rules are not copied from other connectors: the app editor does that, this tool does not. To filter Polar Pixel the same way as Shopify, add the same rules to the "polar-pixel" connector explicitly. Required inputs: - conversation_id - title - views

delete_custom_dimension

ChatGPT
Delete an existing custom dimension. Deletion is permanent (hard delete); only the MCP audit log retains the payload for recovery. CRITICAL (non-self-created items): If you did NOT create this dimension in the current conversation, BEFORE calling this tool you MUST: 1. Call get_custom_dimension_usages to see what depends on it. If non-empty, tell the user in plain English what would be orphaned (names + types, not just counts) and stop — deletion is blocked anyway, so don't try. 2. If the usages list is empty, tell the user the dimension's name (and id, for reference) and ask whether to proceed with deletion. 3. Once the user gives a clear affirmation ("yes", "go ahead", "delete it", etc.), call this tool. You do NOT need the user to repeat the id. For items you created in the CURRENT conversation: delete freely when asked. The server also runs an in-use check; if any references exist it returns a blockedBy list. Relay that list to the user and stop — do not cascade or work around the block. Required inputs: - conversation_id - id (the custom_<N> id)

delete_custom_metric

ChatGPT
Delete an existing custom metric. Deletion is permanent (hard delete); only the MCP audit log retains the payload for recovery. CRITICAL (non-self-created items): If you did NOT create this metric in the current conversation, BEFORE calling this tool you MUST: 1. Call get_custom_metric_usages to see what depends on it. If non-empty, tell the user in plain English what would be orphaned (names + types, not just counts) and stop — deletion is blocked anyway, so don't try. 2. If the usages list is empty, tell the user the metric's name (and id, for reference) and ask whether to proceed with deletion. 3. Once the user gives a clear affirmation ("yes", "go ahead", "delete it", etc.), call this tool. You do NOT need the user to repeat the id. For items you created in the CURRENT conversation: delete freely when asked. The server also runs an in-use check; if any references exist it returns a blockedBy list. Relay that list to the user and stop — do not cascade or work around the block. Required inputs: - conversation_id - id (the custom_<N> id)

delete_dashboard

ChatGPT
Delete a dashboard. Defaults to mode "only-empty", which refuses when the dashboard still holds blocks. To delete a populated dashboard you must pass an explicit mode: - "only-empty" (default) delete only if it has no blocks - "delete-elements" delete the dashboard AND its reports and key-indicator sections - "move-elements" move the blocks onto target_dashboard_id, then delete this dashboard CRITICAL: this is destructive. BEFORE calling this tool you MUST: 1. Call get_dashboard_details and tell the user, in plain English, the dashboard's title, the names (not just the count) of the blocks it holds, and what the chosen mode does to them. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. The only exception is a dashboard you created earlier in this same conversation that still holds no blocks. Required inputs: - conversation_id - dashboard_id

delete_document

ChatGPT
Delete a document. CRITICAL: this is destructive. BEFORE calling this tool you MUST: 1. Call get_document and tell the user, in plain English, the document's title. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. The only exception is a document you created earlier in this same conversation. Required inputs: - conversation_id - document_id

delete_folder

ChatGPT
Folders group dashboards in the Polar left-hand nav. A folder has a key, a title and an ordered list of dashboards. list_folders also returns the built-in "ki" and "report" nav sections and the orphan buckets under those string keys — they are not editable folders, so only numeric folder keys can be created, renamed, deleted or used as a move target. Deletes a folder. The dashboards it held are not deleted — they return to the ungrouped list. CRITICAL: BEFORE calling this tool you MUST: 1. Call list_folders and tell the user, in plain English, the folder's title and the names (not just the count) of the dashboards that will move to the ungrouped list. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. The only exception is a folder you created earlier in this same conversation that holds no dashboards. Required inputs: - conversation_id - folder_key

delete_label

ChatGPT
Deletes a label and removes it from every metric that carries it. The metrics themselves are not deleted. Deletion is permanent. CRITICAL: this is destructive. BEFORE calling this tool you MUST: 1. Call list_labels and tell the user, in plain English, the label's name and the names (not just the count) of the metrics that will lose it (resolve ids to titles with get_metrics or list_custom_metrics when you do not already know them). 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. The only exception is a label you created earlier in this same conversation and that no metric carries yet.

delete_report

ChatGPT
Delete a report from the workspace. This is graph-scoped: the report is stripped from every dashboard that shows it and from every schedule that sends it, not just from the dashboard you were looking at. There is no per-dashboard variant of this tool — to take a block off one dashboard while keeping it elsewhere, pass that dashboard's remaining blocks to update_dashboard. Without force, a report that a schedule still sends is refused, and the refusal names those schedules. Dashboards never block the delete: the block disappears from each of them either way. CRITICAL: this is destructive. BEFORE calling this tool you MUST: 1. Tell the user, in plain English, the report's title and the names of the dashboards that show it (read them with get_dashboard_details). 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. 3. Never pass force: true on the first call. If the call is refused because schedules send the report, name those schedules to the user and ask again before retrying with force: true. The only exception is a report you created earlier in this same conversation that no dashboard shows and no schedule sends. Reshaping an existing block is a job for update_report, not for removing the block and adding a replacement: a re-added block is a different report with a new id, so it drops out of every schedule that referenced the original. Required inputs: - conversation_id - report_id

delete_views_collection

ChatGPT
Delete a views collection and every view in it. Only a workspace administrator with the view.delete_collection permission can delete a collection; anyone else is refused. Deleting breaks every schedule, alert, annotation and role grant that uses one of its views. Without force, a collection that something still uses is refused, and the refusal lists what uses it by type and title. CRITICAL: this is destructive. BEFORE calling this tool you MUST: 1. Tell the user, in plain English, which views will be removed and what uses them: the refusal names every schedule, alert, annotation and role grant that uses them. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. 3. Never pass force: true on the first call. Only retry with force: true after the user confirms what the refusal named. The only exception is a collection you created earlier in this same conversation that nothing uses. Required inputs: - conversation_id - collection_id

fetch_live_connector_data

ChatGPT
Reads a connector the workspace has already connected directly from its provider API, bypassing Polar's warehouse. Always prefer Polar's warehoused data through generate_report whenever it can answer the question, including when the requested range contains today or the last few hours. Recency alone is not a reason to use this tool. Use this tool at your discretion only when at least one of these is true: - the user explicitly asks for live data, provider-side data, or a direct API read - the answer requires a metric, field, breakdown, or current entity state that the warehouse does not expose but one of the connector's live operations does - the answer materially depends on data newer than the relevant account's warehouse coverage When freshness could change the answer, call get_connector_statuses first. Compare the requested period with the account's data_available_through in its timezone; that field is the warehouse coverage boundary, while last_completed_sync_at is not. Prefer generate_report for the covered period and use this tool only for the necessary uncovered portion. This also applies to a freshly connected account that is still backfilling. A user saying "from Meta" or "from Google" names the source, not necessarily the live API — Polar's warehouse already contains that source's data. If the user explicitly asks for live or API-side figures ("read the API directly", "what does Meta itself report"), honour that for any range within the caps. State when figures came from the provider API rather than Polar's warehouse, and do not silently combine the two when their metric definitions or currencies differ. Warehouse reads are faster, cheaper, historically consistent, and model the metrics shown in Polar; live reads spend the workspace's provider API quota, which its syncs also depend on. Connectors that support live reads: Meta (Facebook Ads) — connector_key "facebook-ads" Google Ads — connector_key "google-ads" Google Analytics 4 — connector_key "google-analytics-four" Amazon Seller Central — connector_key "amazon-selling-partner" Amazon Vendor Central — connector_key "amazon-vendor-central" Amazon Ads — connector_key "amazon-ads" TikTok Ads — connector_key "tiktok-ads" Shopify — connector_key "shopify" Klaviyo — connector_key "klaviyo" Google Sheets — connector_key "google-sheets" Google Search Console — connector_key "google-search-console" Instagram Business — connector_key "instagram-business" Facebook Pages — connector_key "facebook-pages" Microsoft Advertising (Bing Ads) — connector_key "bing-ads" Custom integrations — connector_key "custom" (workspaces that have AI-built custom datasources; datasource_id is REQUIRED, and each custom datasource declares its own operations — describe lists them). Every source listed under custom_connectors in your context IS one of these: Superfiliate, ShopMy, AppLovin and the like are read with connector_key "custom" plus their datasource_id. Never conclude a named custom source lacks live reads without running describe against its datasource — calling without datasource_id lists the workspace's custom datasources by name. Discover a connector's operations before the first read: call this tool with operation "describe" — it returns one row per operation with its summary, exact params object (defaults included) and row fields. Pass params: { operation: "<name>" } to narrow to one operation. Validation errors also name the valid values, so a wrong guess is corrected in one step. Inputs: conversation_id (from get_context), connector_key, operation, params (the operation's own object, as documented by describe), and optionally datasource_id to scope to one connected account — get_connector_statuses lists the datasource_ids for native connectors. Omitted on a native connector, the read covers every connected account at once (up to 5; each row then carries account_label); omitted on connector_key "custom", the call lists the workspace's custom datasources by name instead of reading. Returns { rows, row_count, truncated, notes, fetched_at, cached, account_label }. rows follow the…

generate_report

ChatGPT
Generate a comprehensive analytics report. Required inputs: - conversation_id, - metrics (comma-separated keys exactly as received from the metrics field of the get_context tool), - dimensions (comma-separated keys exactly as received from the breakdowns field in list_dimensions), - dateRangeFrom/dateRangeTo (YYYY-MM-DD), - ordering (comma-separated key-value pairs of key and direction) - granularity (none|day|week|month|year), - settings (e.g. attribution model - pass empty object if non-attribution query) - rules (JSON string like {"country":[{"value":["US"],"operator":"IS"}]}), - metricRules (JSON string like {"shopify_sales_main.raw.total_orders":[{"operator":"GREATER","value":["0"]}]}), - views (comma-separated ids exactly as received from the view field of list_dimensions), - limit (optional, integer): Max rows to return (default 1000). ALWAYS set an explicit limit appropriate to the query — for browsing/listing choose a single integer between 10 and 25, for analysis use only as many rows as needed. Fetching too many rows wastes context. The full dataset is always available via the returned deep link. - comparisonPeriod (optional): Compare against a previous period. Values: previousPeriod (same duration before current range), previousYear, previousMonth, or range (requires comparisonDateRangeFrom/To). - comparisonDateRangeFrom/comparisonDateRangeTo (optional, YYYY-MM-DD): Custom comparison date range, only used when comparisonPeriod is "range". Output: JSON report table data, totals data and a deep link to display the same table (with totals) in-app. When comparison is requested, also returns compareTableData, compareTotalData, and comparisonDateRange. Only include attribution_model in settings when generating reports that involve marketing channel attribution or cross-channel analysis. For Shopify metrics (sales, orders, product data, customer segments), pass an empty settings object if you do not know the attribution model. Totals data: returns a row of totals for every metric in the metrics list. Always use these provided totals; do not recompute sums yourself unless you really need to. For grouped/segmented reports (e.g., “top 5 cities”), sum the returned rows and verify they never exceed the corresponding server totals; if there’s a mismatch, prefer the server totals and call it out as a reconciliation note. For ungrouped reports, display the returned totals directly. When presenting percentages or shares, compute them as row_value / total_value (not from independently summed rows) If a total is missing for a metric, state that totals are unavailable and avoid inferring them. When the data is useful, include the exact deep link so the customer can explore further on their own in-app. Use the 👉 emoji to make it very clear that it's a link. Use generate_app_report_link to generate other links or visualizations as needed. If you got the report from an element in the get_dashboard_details tool, use those deep links. This shouldn't prevent you from generating an artifact with other content if needed, the goal is for the user to link to the Polar report if they want to validate the data or explore further. After responding, ask user for a 1-10 rating, then call rate_report. Gating: this tool refuses with { error: true, reason: "needs_activation" | "needs_connectors", message, book_call_url, connectors_url } when account_status from get_context indicates the tenant is not yet activated or has no loaded connector data. Don't retry on the same tenant in the same conversation — deliver the message verbatim or paraphrase, point the user at the URL, and stop.

get_api_access_status

ChatGPT
Reports whether the workspace has an API key for a direct integration. Results (status): - "active": the key is saved; label, base_host, allowed_methods, graphql, read_post_paths, has_playbook and created_at describe it. Read it with fetch_live_connector_data, connector_key "direct"; when has_playbook is false, explore it and call save_api_playbook. - "pending": a key-entry page was sent and not used yet; expires_at says until when. - "none": no key and no open request. request_api_access asks for one. Inputs: conversation_id (from get_context), integration_key.

get_connector_setup_status

ChatGPT
Checks whether a connection flow started with start_connector_connection has been completed by the user. Returns { status, connector_key, datasource_id, error_message, connector_status }: - status: pending (the user has not finished yet), completed, failed (the provider auth failed — error_message says why), or expired (the link is older than 24 hours; start a new one). - connector_status: the live per-account health snapshot for this connector (same shape as get_connector_statuses entries). Check it even when status is pending — form-based and multi-account connectors can finish in the app without the session being stamped, so a new account appearing here means the connection went through. After completion it also tells you whether the account still needs setup (e.g. selecting ad accounts on the connector page) or is already syncing history. Poll this when the user says they finished, or before summarizing — do not poll in a tight loop. Required input: conversation_id (from get_context) and setup_session_id (from start_connector_connection).

get_connector_statuses

ChatGPT
Detailed per-account connector health from /api/datasources/statuses. Use this to drill into the rollup returned by get_context.connectors when the user asks why a connector is Warning/Broken/Syncing history, or to compose remediation prose. Each entry is { key, health, connections, accounts[] }. connections is the per-status breakdown of this connector's accounts (sparse — absent keys mean zero). accounts[] carries { datasource_id, account_name, health, issues, last_completed_sync_at, data_available_through, timezone, data_integrity }. data_integrity is a rollup of the account's data integrity checks: { ok, failed, total } counts, or null when no check signal is available. "ok" includes both exact matches and within-tolerance results — both pass from the user's perspective. For the per-check breakdown (name, status, match rate, in/out-of-tolerance days), call get_data_integrity_report({ datasource_id }). health is one of: healthy | syncing_history | warning | broken | paused | pending. The connector-level health is the worst across its accounts (except all-paused → paused). issues[] is a structured array. Compose the prose yourself from the kind: - error: { kind: "error", message } — generic plumbing error surfaced verbatim from the connector - needs_setup: account is connected but no sub-account is selected; user should finish setup - missing_scopes: { kind: "missing_scopes", scopes } — the connector is missing OAuth scopes; user should reconnect granting these scopes - delayed: no sync today past the morning threshold in the connector's timezone; surface to the user. Hourly-refresh staleness is not flagged until the team calibrates a per-pipeline threshold. - drift_summary: { kind: "drift_summary", failed_count, tolerated_count, total_count } — at least one data integrity check failed (its match rate is below the threshold). Tell the user "X of Y checks failed" (the wording the data integrity report uses) and link to the report. tolerated_count is informational only; those days are within tolerance and the user sees them as "OK". - drift_verified: { kind: "drift_verified", tolerated_count, total_count } — every data integrity check passed (some may be within tolerance, which the user sees as "OK"); surface as positive reassurance, do not flag as a problem Never speculate about lag from last_completed_sync_at alone — only act on explicit issues[] entries. Deep-link pattern (do NOT slugify account names): /connectors/<connector-key> — connector page /connectors/<connector-key>/<datasource_id> — specific account row, where datasource_id is the UUID from accounts[].datasource_id /connectors/<connector-key>/<datasource_id>/data-integrity — data integrity report (full pass/fail breakdown + drill-down per check) Example: /connectors/facebook-ads/8f3a1c0e-44d6-4d8a-9f8b-7c2a1e5d9a40 Required input: conversation_id (from get_context). Optional: connectorKeys to narrow the response to a subset of connectors from get_context.connectors.

get_context

ChatGPT
Run this tool once at the beginning of each conversation to prepare the analytics environment. It returns a combined JSON object with five elements: conversation_id: Conversation ID context: Workspace context { currency, timezone, brand_name (if none is set, it defaults to the email of the first registered user, often the brand’s domain, sometimes a personal email) }; account_status: { activation: 'pending_call' | 'call_booked' | 'activated', data: 'none' | 'loading' | 'ready', book_call_url, connectors_url, message } describing whether the tenant is activated and whether their data is loaded. If activation is not 'activated', tell the user to book the setup call at book_call_url before doing anything else — booking is the highest-priority next step and data tools will refuse until activation completes. Otherwise, if data is not 'ready', tell the user to connect data sources at connectors_url. The 'message' field is a ready-to-deliver explanation; surface it verbatim or paraphrase, but always include the relevant URL. When account_status.message is non-empty, deliver it before any analytics work; connectors: Canonical per-connector rollup. Each entry { key, health, connections } where health is one of healthy | syncing_history | warning | broken | paused | pending (worst across accounts; all-paused → paused). connections is the per-status breakdown of this connector's accounts — only includes health states with at least one account, absent keys mean zero. Total account count = sum of connections values. When any connector is warning, broken, or syncing_history, mention it to the user and offer the deep link. Call get_connector_statuses to drill into specific connectors and compose remediation prose from the structured issues[]. polar-pixel appears in this list like any other connector — health syncing_history means it is still warming up; healthy means it has matured. Note: the canonical health currently only surfaces daily delay signals (no sync today past the morning threshold in the connector's timezone). Hourly-refresh staleness is not flagged until the team calibrates a per-pipeline threshold. Do not speculate about lag from last_completed_sync_at alone; act only on explicit issues[]. custom_connectors: Array of { key } for connectors that have queryable data tables but no live monitoring (custom imports, data warehouses, legacy entries). Treat their health as unknown — never claim they are healthy or broken. connector_status: DEPRECATED — superseded by 'connectors' and 'custom_connectors'. Kept only for backward compatibility and will be removed in a future MCP version. Prefer 'connectors' for health and 'get_connector_statuses' for detail. Legacy shape: array of { name, status, until } where status is 'historical' (data not ready), 'incremental' (data ready up to 'until'), or 'paused' (data exists but syncing is paused — reports use data up to 'until'). Customers can manage connectors at https://app.polaranalytics.com/connectors. If a polar-pixel legacy entry has is_warmed_up=false, the pixel needs attention. Open your final answer with a bold data-quality warning before any numbers. If installed_at is a date, say the pixel is still warming up (give installed_at as the install date) and conversion / attribution numbers aren't statistically meaningful yet — suggest letting it accumulate for at least a couple of weeks before drawing conclusions, with the actual time depending on traffic volume and industry. If installed_at is null, say the pipeline last produced data on polar-pixel 'until' and may not be currently flowing — numbers may be stale. Deep-link pattern for the Connectors area (use canonical 'connectors' key and accounts[].datasource_id from get_connector_statuses; never slugify account names): https://app.polaranalytics.com/connectors/<connector-key> for a connector page, https://app.polaranalytics.com/connectors/<connector-key>/<datasource_id> for an account row. Example: https://app.polaranalytics.com/connectors/facebook-ads/8f3a1c0e-44d6-4d8a-9f8b-7c2a1e5d9a40. custom_dimensions: Initial page of the te…

get_custom_dimension_details

ChatGPT
Used to discover what filtering a custom dimension does. Note: When the user is only looking to use the custom dimension and it's clear which custom dimension they're referring to, looking up its details is superflous. Required inputs: - conversation_id - id (the id of a custom dimension, exactly as obtained from the custom_dimensions field of get_context) Returns the custom dimension with its definition (rules and connector filters).

get_custom_dimension_usages

ChatGPT
List every tenant-owned object that currently depends on a given custom dimension — reports, custom metrics, and KI sections. MUST be called before any update_custom_dimension or delete_custom_dimension attempt on a dimension that was NOT created in the current conversation. Use the result to tell the user in plain English what the change would affect (e.g. "This dimension is used by 3 reports and 1 KI section — these are the names…") before asking for confirmation. Returns { usages: [{ id, title, type: "report" | "custom_metric" | "ki_section" }] }. An empty list means nothing currently depends on it. Required inputs: - conversation_id - id (the custom_<N> id of the dimension)

get_custom_metric_details

ChatGPT
Fetch the full formula of a single custom metric by id. Use when you've found a candidate custom metric via list_custom_metrics (or get_metrics) and need its elements + formatting to judge whether it matches the user's intent before using or extending it. Required inputs: - conversation_id - id (the custom_<N> id) Returns { id, label, description?, elements, formatting }.

get_custom_metric_usages

ChatGPT
List every tenant-owned object that currently depends on a given custom metric — reports, other custom metrics, and KI sections. MUST be called before any update_custom_metric or delete_custom_metric attempt on a metric that was NOT created in the current conversation. Use the result to tell the user in plain English what the change would affect (names, types, count) before asking for confirmation. Returns { usages: [{ id, title, type: "report" | "custom_metric" | "ki_section" }] }. An empty list means nothing currently depends on it. Required inputs: - conversation_id - id (the custom_<N> id of the metric)

get_dashboard_details

ChatGPT
Use this tool when a user references a specific report inside Polar. Retrieve the full definition of a single dashboard, including all its elements. Customers generally refer to the dashboards and/or elements by their titles. In order to get further details on a specific report, please use get_dashboard_details. Required inputs: - conversation_id Output: A single dashboard with the details of its elements, along with deep links. If a dashboard or a report has a deep link, always include that link when referring to the dashboard/report. The table data has a limit of 50 applied. More rows can be retrieved with the generate_report tool as needed. If the tenant is not yet activated or has no connector data loaded, this tool returns the dashboard structure (titles, layout, deep links) but skips the per-element data and includes data_skipped: true plus a reason and message from account_status in get_context. When you see data_skipped, deliver the message verbatim or paraphrase, point the user at the URL, and do not fabricate numbers.

get_data_coverage

ChatGPT
What software this store runs that Polar does not ingest yet, found from Polar's own Shopify data (order and customer tags, sales channels, discount codes, referring sites, pixel utm_source over the last 12 months), the public storefront (third-party scripts, Google Tag Manager container) and public DNS (SPF, DMARC, domain verification). Use it (1) when get_metrics returns nothing for a tool or app name, before saying Polar lacks the data; (2) to answer "what data is missing", "what else should we connect", "what tools do we run"; (3) when a report needs a source Polar has no connector for. Each entry is { key, label, category, bucket, connector_key, orders, net_sales, currency, evidence[], next_step, last_seen }. bucket is one of: - stalled: the connector is connected but paused or has stopped loading, and orders with this tool's signal kept coming after the last loaded date. next_step is { kind: "reconnect", url } to the connector page. - connect: Polar has a self-serve connector that is not connected. next_step is { kind: "connect", url } to the connect page. - build: no self-serve connector; the AIDE can build it. next_step is { kind: "build" }: offer aide_start with the tool as the goal. - connected: detected and connected; listed only with bucket "connected" requested. orders and net_sales are 12-month figures for orders carrying the tool's signal in Polar; they come from the largest single matched breakdown row, so they are a lower bound: say "at least". They are null when the only evidence is the storefront or DNS, and when only customer tags carry orders (customer tags count every order of every tagged customer, so they never size a tool). evidence[] is { signal, value } (e.g. { signal: "order_tag", value: "ShopMy" }). checked_at is when the last run finished; null when the store has not been checked yet (new workspace, Shopify history still loading): say so and do not guess. passes says which checks ran: a failed pass means the check did not run, never that nothing was found. Never present a tool as missing data when its bucket is connected. Competitor analytics tools are not returned. Required input: conversation_id (from get_context). Optional: bucket to filter.

get_data_integrity_report

ChatGPT
Per-check breakdown of the latest data integrity report run for a single account. This is the same report the user sees at /connectors/<connector-key>/<datasource_id>/data-integrity. Use it as the drill-in when get_connector_statuses returns a non-null data_integrity rollup on an account and the user asks for specifics (which check, its match rate, which dates were out of tolerance). Returns { tests: [{ name, status, errorRatio, matchedValues, unmatchedValues }] }. Each test is a "check" on the report. What drives a check's status is its overall match rate, not individual days. match rate = (1 - errorRatio), shown to the user as a percentage (e.g. errorRatio 0.109 → "89.1% match rate"). A check is marked Failed when its match rate is below the configured threshold; a single day out of tolerance does not fail the check on its own. Use "match rate" when talking to the user — that is the wording on the report. status values: - SUCCESS — matches the source exactly. The report shows this as "OK". - WITHIN_TOLERANCE — differs from the source but within the configured tolerance. The report also shows this as "OK" (no distinction from SUCCESS on the public surface). - FAILED — the match rate is below the threshold. The report shows this as "Failed". - SKIPPED — the check did not run for this account. - ERROR — the check could not execute (e.g. API call failed). A check that ran but had zero days to compare (both matchedValues and unmatchedValues empty) is shown as "No data" on the report; describe it that way instead of OK or Failed. errorRatio is the magnitude of mismatch as a fraction of expected (0 = exact match); the report surfaces its inverse as the match rate. matchedValues and unmatchedValues are per-day breakdowns keyed by date with { expected, current } pairs (numbers, or "No value" when the source returned nothing for that day). On the report each matched day is labeled "In tolerance" and each unmatched day "Out of tolerance". Only the latest incremental run is returned. Historical runs are not surfaced. Required input: conversation_id and datasource_id (the UUID from get_connector_statuses accounts[].datasource_id).

get_dimension_values

ChatGPT
Get the possible values for a given dimension. Inputs: - dimension: required. The dimension to get the values for. - tables: required. The tables to get the values from, as an array of strings. - search: optional. The search text to filter the values. If set, only the values containing the search text will be returned. Returns a JSON array with the possible values for the dimension, up to a maximum of 100 values.

get_document

ChatGPT
Read a document: its title, folder, visibility, version and elements, in the order update_document takes them. A document is a title and a grid of elements, filed in folders next to dashboards. Share a written report as a document instead of an HTML report link. Required inputs: - conversation_id - document_id

get_html_block_guide

ChatGPT
Call this once without topic before creating or editing an html dashboard block. Returns the window.polar runtime API, the exact row shape a block receives, how to pick metric keys, worked examples, and the core visual design procedure with a topic index. Then call it again with each topic the index points to for the visual you are building; a topic call returns only that topic.

get_metrics

ChatGPT
List the metrics available for a list of sources. Connector metrics are returned as { key, label }. Custom metrics carry { key, label } plus ONE of: - summary: human-readable one-line purpose (preferred when authored or AI-summarized) - refs: array of underlying metric labels the formula touches. Use as a structural hint when no summary exists — a clumsy label like "ROAS v2 jamie" with refs ["Total ad spend", "Net sales"] is very likely a return-on-ad-spend metric. Match on label + summary + refs together when judging whether an existing custom metric already covers the user's intent. Use the exact 'key' values in other tools—do not modify them. If you are unsure which metric to use, ask the user to confirm. The full elements + filters formula is NOT inlined; call get_custom_metric_details(id) to fetch the full body for any candidate worth inspecting. The custom-metrics list is capped at the 300 most recently created/updated custom metrics. For older or oddly-named metrics beyond this initial set, call list_custom_metrics(q?, offset?, limit?) — paginated + searchable by case-insensitive substring on title and summary. If the user references a metric by name and you don't see it here, search via list_custom_metrics before creating a duplicate. SOURCES: A source is either a connector name (from connector_status) or 'custom-metrics'. IMPORTANT: Include 'custom-metrics' in your first call to discover custom metrics for the conversation. Custom metrics contain critical business logic that the user expects you to know about. Omitting them risks ignoring user-defined metrics and giving incorrect answers. SOURCE SELECTION RULES: 1. If the user names a data source (e.g., "Google Analytics", "GA4", "Shopify", "Facebook Ads", "Meta"), include the matching connector key from connector_status. Map common phrasing to the exact connector key: - "GA4", "Google Analytics", "Google Analytics 4" → google-analytics-four - "Facebook", "Facebook Ads", "Meta", "Meta Ads" → facebook-ads - "Google Ads", "Adwords" → google-ads - "Shopify" → shopify - "TikTok", "TikTok Ads" → tiktok-ads - "Klaviyo" → klaviyo - "Polar Pixel", "Pixel" → polar-pixel When in doubt, match case-insensitively against the name field in connector_status. 2. If the question spans multiple sources (e.g., "compare Shopify revenue to Facebook spend"), include ALL referenced connectors. 3. If the scope is ambiguous, include every connector that could plausibly answer the question — do not default to one connector. It is better to over-include relevant connectors than to miss the one the user cares about. 4. Always include 'custom-metrics' on the first call. EXAMPLES: - User: "look at google analytics four landing page data" → sourceList: "google-analytics-four,custom-metrics" - User: "Shopify revenue last 30 days" → sourceList: "shopify,custom-metrics" - User: "blended ROAS this month" → sourceList: every ad-platform connector + shopify + custom-metrics - User: "how is my business doing" → sourceList: every connector in connector_status + custom-metrics CRITICAL: Custom metrics (keys starting with 'custom_') contain USER-DEFINED business logic specific to this account. MANDATORY RULE: When a user requests a metric by name or concept: - Search the custom metrics list FIRST for matching labels - If a custom metric matches the user's request, you MUST use it - Standard metrics are ONLY used when no relevant custom metric exists - Using a standard metric when a custom version exists is an ERROR. - Before calling create_custom_metric, verify via this list (and via list_custom_metrics for the long tail) that no existing custom metric already captures what the user wants — prefer reusing an existing metric and cite it by label in your response. METRIC KEY ARCHITECTURE: Metric keys follow different patterns based on their scope: - Table-specific metrics have table prefixes: shopify_sales_main.raw.*, shopify_sales_main.computed.*, facebook_ads_main.raw.* - Blended metrics have NO table prefix (e.g., polar_pixel_conversion_rate, blended_roas, `pi…

get_report

ChatGPT
Returns one report's full definition, the object update_report takes back. A report is a saved Polar query block — a table, a chart or a set of metric cards — defined by the metrics it shows, the breakdowns it groups by and the filter rules it applies. A report is a first-class object: it exists in the workspace whether or not a dashboard displays it, and the same report can sit on several dashboards and schedules at once. The report definition is an object: - metrics: string[] — metric keys from get_metrics. At least one; an empty list is refused. - breakdowns: string[] — dimension keys to group by, from list_dimensions. The key "date" groups by time. - rules: object — filters keyed by dimension key, each value a list of { operator, value } clauses. Pass {} for no filter. - metricRules: object — same shape, applied per metric. - reportMode: one of "table", "bar", "line", "stacked-bar", "stacked-line", "100-stacked-bar", "100-stacked-line", "doughnut", "mixed", "metric-cards", "sparkline-cards". Defaults to "table". - granularity: "day", "week", "month", "quarter", "year" or "none". Set it on every chart that shows a trend over time (line, stacked-line, 100-stacked-line, mixed, sparkline-cards, or bars over time): "day" by default, "week" or "month" when the user asks for it or the range spans many months. Without it the block follows the viewer's granularity picker, which defaults to "none" (Entire Range): one aggregate row, so a line chart draws a single point. Set "none" for totals and for charts broken down by a category rather than by date. - top: { topLimit, direction, topMetric } — keeps the leading rows by one metric, which has to be one of metrics. - ordering: { columnKey, direction }, or a list of those. - dateRange: object — pins the report to its own dates instead of the dashboard's. Omit it to follow the dashboard. tab picks the kind, and only that tab's selection is needed: - { tab: "predefined", predefinedSelection }, where predefinedSelection is one of: "today", "thisWeek", "thisMonth", "thisYear", "yesterday", "lastWeek", "lastMonth", "lastYear", "all", "bfcm2019", "bfcm2020", "bfcm2021", "bfcm2022", "bfcm2023", "bfcm2024", "bfcm2025" Rolling windows such as the last 7 or 30 days are not predefined periods; use the relative tab. - { tab: "relative", relativeSelection: { count, granularity } }, where granularity is one of: "day", "week", "month", "quarter", "year", "whole_week", "whole_month", "whole_quarter", "whole_year" { count: 7, granularity: "day" } is the last 7 days; the whole_ variants count complete periods only. - { tab: "range", rangeSelection: { start, end } } — dates as "YYYY-MM-DD". - compare, displaySettings, colorSetup, targets — optional presentation settings, best carried over from get_report rather than invented. Call list_reports or get_dashboard_details first to get the report id. Reading before writing matters: an update replaces the whole definition, so the safe edit is this definition with the one field changed. Required inputs: - conversation_id - report_id

get_version

ChatGPT
Returns one version from list_versions: its snapshot (the object as it was after that change), what it changed compared to the version before it, and restore_preview, what restoring it would do now. restore_preview has restorable false with a reason when the version can't be restored (it is already the current version, or the object is gone for good). Otherwise it lists: - restores_deleted_object: restoring brings a deleted object back - changes: what restoring changes compared to the object's live state - blocks (dashboards only): each block that does not simply stay as it is, with its outcome. "reverted" puts a report on the dashboard back to its content at that time. "revived" brings back a deleted report under its original id. "recreated_as_copy" creates a new copy, because the original is now on another dashboard or no longer exists. "dropped" leaves the block out, because nothing is left to rebuild it from. A dashboard snapshot lists its blocks in full: an html block with its html, a key indicator with its settings, and so on. A report or key indicator section block is listed by type and id only; call get_report for a report block's content. To see what an edit changed inside a block, compare its snapshot here with the snapshot of the version before it. Required inputs: - conversation_id - version_id

get_view_details

ChatGPT
Used to discover what a view (a segment/set of dimensions pre-configured by the user) does. Note: When the user is only looking to use the view and it's clear which view they're referring to, looking up its details is superflous. Required inputs: - conversation_id - view (the id of a view, exactly as obtained from the view field of get_context) Returns the view with its definition (rules and connector filters).

list_custom_dimensions

ChatGPT
Browse or search the tenant's custom dimensions beyond the first page surfaced by get_context. Sorted by recency-of-update (most recently edited / created first). Pass q to filter by case-insensitive substring on title or summary; omit q to page through everything. USE THIS WHEN: - The user references a custom dimension by name and you don't see it in the get_context list. - You're about to call create_custom_dimension and want to double-check no existing dimension covers the same intent. - You need to enumerate dimensions that touch a particular underlying field (search by the field name as a query — e.g. q="country" surfaces every dimension whose label or summary mentions country). Returns { items: [{ id, label, summary?, refs? }], total, offset, limit, hasMore }. Each item carries the same compact shape as get_context's custom_dimensions slot. Call get_custom_dimension_details(id) to fetch the full whenThen formula for any candidate that looks worth inspecting. Required inputs: - conversation_id Optional inputs: - q (case-insensitive substring; matches title or summary) - offset (default 0) - limit (default 50, max 200)

list_custom_metrics

ChatGPT
Browse or search the tenant's custom metrics. Sorted by recency-of-update (most recently edited / created first). Pass q to filter by case-insensitive substring on title or description; omit q to page through everything. USE THIS WHEN: - The user references a custom metric by name and you don't see it in get_metrics's custom-metrics output. - You're about to call create_custom_metric and want to double-check no existing metric covers the same intent. Returns { items: [{ id, label, summary?, refs? }], total, offset, limit, hasMore }. refs lists the underlying metric keys that the custom metric's formula depends on. The full elements formula is not inlined; the get_metrics tool already returns it for items in the standard custom-metrics page. Required inputs: - conversation_id Optional inputs: - q (case-insensitive substring; matches title or description) - offset (default 0) - limit (default 50, max 200)

list_dashboards

ChatGPT
When a user references a dashboard or report by name, call list_dashboards first to verify existence before generating data. A dashboard is a core building block in the Polar platform. Supported dashboard elements include reports (tables, charts etc.) and key indicator sections (i.e. metric grids). Customers generally refer to the dashboards and/or elements by their titles. In order to get further details on a specific report, please use get_dashboard_details. Required inputs: - conversation_id Output: A list of dashboards and their elements, along with deep links. If a dashboard or a report has a deep link, always include that link when referring to the dashboard/report.

list_dimensions

ChatGPT
Given comma-separated metric names from get_context, return compatible breakdowns, filters and settings. Note: all metrics can use the 'date' dimension, so you do not need to check for it. You need to do this for each new set of metrics, as it can vary greatly. IMPORTANT: If the user explicitly requested a specific breakdown (e.g. "by landing page", "per product") and it does NOT appear in the breakdowns response, your chosen metrics are incompatible with that dimension. Do NOT silently switch to a different breakdown. Instead, call list_metrics_for_dimension with the user's requested dimension to find compatible metrics, then retry with those. Required inputs: - conversation_id Returns a JSON object with three keys: - breakdowns (dimensions that the supplied metrics can be grouped by) - filters (dimensions that the supplied metrics can be filtered by) - settings (key value pairs of settings and possible values) Example: { "attribution_model": [ { "value": "last_click", "label": "Last Click" }, { "value": "linear", "label": "Linear" }, ] If you are looking for a given dimension and you are not sure which one you should use, you need to ask confirmation to the user. Generally, the user do not want to breakdown by IDs, they want to breakdown by names, or titles. Example: sales_channel_name instead of sales_channel. You can use the get_dimension_values tool to get the possible values for some dimensions and figure out which one you should use. CRITICAL: Custom dimensions (keys starting with 'custom_') contain USER-DEFINED business logic specific to this account. MANDATORY RULE: When a user requests a dimension by name or concept: - Search the custom dimension list FIRST for matching labels - If a custom dimension matches the user's request, you MUST use it - Standard dimensions are ONLY used when no relevant custom dimensions exists - Using a standard dimension when a custom version exists is an ERROR. This rule does NOT apply to custom_default dimensions.

list_documents

ChatGPT
List the documents the caller can see, most recently updated first. A document is a title and a grid of elements, filed in folders next to dashboards. Share a written report as a document instead of an HTML report link. Pass folder to list one folder. Pass scope to list drafts only: "mine" for your private drafts, "team" for team drafts, "any" for both. Required inputs: - conversation_id

list_folders

ChatGPT
Folders group dashboards in the Polar left-hand nav. A folder has a key, a title and an ordered list of dashboards. list_folders also returns the built-in "ki" and "report" nav sections and the orphan buckets under those string keys — they are not editable folders, so only numeric folder keys can be created, renamed, deleted or used as a move target. Returns every folder with the dashboards it holds. Call this before creating a folder — reuse an existing one instead of creating a near-duplicate — and before move_dashboard, to get the folder key. Required inputs: - conversation_id

list_labels

ChatGPT
Labels are workspace-defined tags on custom metrics: { name, category, description, color }. Metrics may carry labels: workspace-defined tags { name, category, description } that the workspace's team attached on purpose (for example a "Certified" label under a "Governance" category). A label's description is authoritative guidance on when to use the metric: when several metrics could answer a question, prefer the one whose labels and label descriptions match the question, and mention the label when you cite the metric. Returns every label in the workspace with the ids of the metrics carrying it, custom_<N> for a custom metric and the metric key otherwise. Call this before creating a label (to reuse an existing one instead of creating a near-duplicate) and before changing a metric's labels (to get the label ids).

list_metrics_for_dimension

ChatGPT
Given a dimension key, return metrics that are likely to support it as a breakdown. Use this tool BEFORE picking metrics when the user names a specific breakdown (e.g., "by landing page", "per product"). STRICT RULE: When calling generate_report with this breakdown, use ONLY metrics returned by this tool. Do NOT add metrics from get_metrics — not even computed ones from the same table. A single unsupported metric will cause the breakdown to fail with "cannot apply breakdown by <dimension>". If you want more metric variety, run a second report without the breakdown. Required inputs: - conversation_id - dimension (a single dimension key as it appears in the breakdowns field of list_dimensions) Returns an array of { key, label } metrics. Returns an empty array if the dimension does not exist or no metrics support it. Known gaps: this tool does not yet include blended metrics (no table prefix) or custom metrics (keys starting with custom_). If the user needs those with a breakdown, verify compatibility via list_dimensions.

list_reports

ChatGPT
Lists the reports saved in this workspace, on a dashboard or not. A report is a saved Polar query block — a table, a chart or a set of metric cards — defined by the metrics it shows, the breakdowns it groups by and the filter rules it applies. A report is a first-class object: it exists in the workspace whether or not a dashboard displays it, and the same report can sit on several dashboards and schedules at once. Each row carries the report id, its title, the metrics and breakdowns it shows, its display mode and a deep link. get_report returns one report's full definition, and update_report edits it in place. Reshaping an existing block is a job for update_report, not for removing the block and adding a replacement: a re-added block is a different report with a new id, so it drops out of every schedule that referenced the original. Required inputs: - conversation_id

list_versions

ChatGPT
Lists the version history of custom dashboards, reports, custom metrics, custom dimensions and views collections, newest first. Every write records a version, whether a person made it in the app (source "app") or an AI agent made it (source "mcp"). With object_id: the history of that one object. A dashboard's history also includes edits to the reports on it, listed as their own report versions. Works for deleted objects too, since their history survives the delete. Without object_id: the latest versions of every object of object_type in the workspace. This is how to find a deleted dashboard, metric, dimension or views collection, which the other list tools no longer show: its entries have object_deleted true. Reports are not listed this way; a report's versions, deleted or not, are in the history of the dashboard it is on. Each entry carries version_id, title, operation ("create", "update", "delete", "restore" or "initial version", the state before the first recorded edit), source, changed_by (the person's name, else their email; null when no person made it or their account no longer exists), changed_at, is_current (the live state of an existing object) and changes (what this version changed compared to the previous one listed for the same object; empty when the previous one is not in the list). A dashboard's blocksAdded, blocksRemoved and blocksEdited name each block by type, id and title; a blocksEdited block also lists the fields that changed, such as "settings.html". A report's definition change lists the fields that changed, such as "metrics" or "breakdowns". A metric's format change is its value formatting; formula means the calculation itself changed. Only versions the caller is allowed to see are listed: a custom role sees a dashboard's versions only when it can see every block in it. Required inputs: - conversation_id - object_type

list_views_collections

ChatGPT
Lists the workspace's views collections with their views, and the connectors a view can use: for each connector its stores (identifier and title) and the filter_keys its rules accept. A view is connectors + stores + rules. A view id is "<collection_id>-<item_id>", the same id get_context and get_view_details use. Each collection carries its version: pass it as expected_version to update_views_collection. A connector entry with stores_error could not list its stores; views on that connector cannot be created or changed until it resolves. Required inputs: - conversation_id

move_dashboard

ChatGPT
Move a dashboard into a folder. Call list_folders first to get the folder key, and create_folder when no suitable folder exists yet. Required inputs: - conversation_id - dashboard_id - folder_key

move_document

ChatGPT
Move a document into a folder, which shares it with the workspace, or out of its folder with folder null, which makes it a draft: a team draft over an API key, a private draft otherwise. Call list_folders first to get the folder key. Required inputs: - conversation_id - document_id - folder

move_element_to_dashboard

ChatGPT
Move an EXISTING report or key-indicator section onto a dashboard. The block is appended to the target dashboard and taken off every other dashboard that holds it, so it ends up on the target dashboard only. Use this for a block that already exists — a report the user just built and saved, or one that lives on another dashboard. A block already on the target dashboard and nowhere else is left as it is. To create a new block, pass it inside elements on create_dashboard or update_dashboard instead. Required inputs: - conversation_id - dashboard_id - element_type ("report" or "kiSection") - element_id

rate_report

ChatGPT
Submit the user's rating for your previous analysis. ONLY call this after the user explicitly provides a rating. Do not rate yourself. Inputs: - rate (string number 1-10), - reflexion (your analysis of their goal and why they are rating it this way). Output: confirmation message.

read_api_docs

ChatGPT
Reads an API documentation URL and extracts the machine-readable OpenAPI/Swagger specification behind it. Use it whenever a documentation page is JavaScript-rendered (Swagger UI, Redoc, RapiDoc, Scalar, Stoplight viewers return a near-empty HTML shell to a plain page fetch): the tool finds the spec URL inside the page shell — or probes conventional spec paths, including versioned API-base conventions like /api/v1/docs.json — fetches the spec, and returns a compact digest: authentication schemes, servers, every endpoint with its parameters, and request/response shapes. The digest is bounded and never overflows the context budget. Also accepts a direct spec URL (openapi.json / swagger.json / *.yaml). When no spec exists it falls back to LLM-friendly text endpoints (llms.txt, llms-full.txt, the page's .md variant) and returns their markdown as a text_content result. When a vendor's docs SITE is unreadable, call this tool again with the API BASE URL itself (e.g. https://api.vendor.com/v1) — API hosts often serve their own reference at conventional paths. Pass path_filter (a substring of an endpoint path) to expand full parameter/schema detail for matching endpoints while keeping the rest compact. REST/OpenAPI only — it cannot parse WSDL (SOAP) documents.

reorder_dashboards

ChatGPT
Reorder dashboards, or move a block from one dashboard to another, by describing the item that moves and the item it lands next to. from_item_type / to_item_type are "dashboard" or "ai-dashboard" when reordering whole dashboards in the nav, and "report" or "kiSection" when moving a block. alignment says whether the moved item lands "before" or "after" the target. Call list_dashboards (and get_dashboard_details for block ids) first so every id you pass is real. Required inputs: - conversation_id - from_dashboard_id, from_item_id, from_item_type - to_dashboard_id, to_item_id, to_item_type

resolve_date_range

ChatGPT
Converts a date expression into exact dates in the workspace timezone, honouring the workspace week start, and returns the weekday name of each boundary. It also returns the workspace's current date and weekday, which are the anchor every relative expression is resolved against. One call resolves one period. Its dates are the ones generate_report will agree with, since both read the same workspace calendar. Anything it cannot read exactly, it declines and returns expressions to try instead, rather than guessing. SYNTAX: a base period, optionally followed by one shift. BASE PERIODS today, yesterday, N days ago a single day last N days last N weeks, months, quarters, years last N whole weeks, months, quarters, years completed periods only week to date, month to date, quarter to date, year to date this week, last week, week before last this month, last month, month before last this quarter, this year monday this week, friday last week a weekday inside a named week, which may still be in the future this week most recent tuesday the latest one before today 2026-07-28, or 2026-07-20 to 2026-07-26 explicit dates SHIFTS (appended, arithmetic, applied left to right) - N days, - N weeks, - N months, - N quarters, - N years last week - 1 year the same calendar week a year earlier last week - 52 weeks the weekday-aligned week a year earlier Ranges run up to and including today by default, matching Polar's date picker. Appending 'excluding today' stops at yesterday instead. The dayCount field is the period's true length: the picker fixes the start and moves the end, so 'last 7 days' covers 8 days when today is included. Every result carries an interpretation stating in words what was resolved, so the reading is visible alongside the dates.

restore_version

ChatGPT
Restores a dashboard, report, custom metric, custom dimension or views collection to a version from list_versions. A deleted object comes back under its original id. The restore is itself recorded as a new version, so it can be undone by restoring the version that was current before it. CRITICAL: this overwrites the object's current state. BEFORE calling this tool you MUST: 1. Call get_version and tell the user, in plain English, which object and which point in time this restores, and what its restore_preview says will change, naming every block that will be recreated as a copy or dropped. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "restore it"). Never infer consent from the original request. Never call it when restore_preview says restorable is false. Returns the restored object's object_type and object_id, plus recreated_blocks (blocks brought back as new copies) and dropped_blocks (blocks left out). Tell the user about both. Required inputs: - conversation_id - version_id

revoke_api_access

ChatGPT
Deletes the workspace's API key for a direct integration, when the person asks to remove or revoke it. Reads through it stop at once. Only people whose Polar role can edit connectors can revoke a key. Results (status): - "revoked": the key is gone. - "not_found": no active key with this integration_key. - "link_required": their Slack account is not linked to Polar yet; they were shown a private button to link it. - "forbidden": their Polar role cannot manage API keys on this workspace. Inputs: conversation_id (from get_context), integration_key.

save_api_playbook

ChatGPT
Saves how to read a direct integration, so every later conversation, in Slack or in Polar, reads it without exploring the API again. Call it once you have called the API's endpoints yourself with fetch_live_connector_data (connector_key "direct") and they answered, and again whenever you learn something that changes how to read it. Each save replaces the playbook; the one it replaced is kept. Only people whose Polar role can edit connectors can save it. Write the playbook as short markdown for another assistant, from what the API actually returned, never from guesses: - What it holds and which questions it answers. - Each endpoint you used: method, path (relative to the base URL, with {placeholders}), the parameters that matter, and the fields worth reading in the answer. - How to find things: search or list endpoints, filters, sorting, and how ids are passed between calls. - Pagination and limits: cursor or page parameters, page size, rate limits you met. - Traps: errors you hit and what fixed them, required headers or versions, date formats. - One worked example: the calls, in order, that answer a typical question. Never put an API key, token or password in it. Results (status): - "saved": the playbook is stored; read_post_paths lists the POST endpoints now treated as reads. Tell the person which ones. - "not_found": no active API key with this integration_key. - "link_required": their Slack account is not linked to Polar yet; they were shown a private button to link it. - "forbidden": their Polar role cannot manage API keys on this workspace. Inputs: conversation_id (from get_context), integration_key, summary (one or two sentences: what the API holds and when to use it), playbook (markdown, at most 12000 characters), read_post_paths (optional; replaces the POST endpoints that only read).

select_workspace_for_session_from_name

ChatGPT
Resolves a fuzzy workspace name (or partial name) to one or more canonical workspace IDs. The resulting workspaceId is the value to pass as the workspace_id parameter on subsequent tool calls (get_metrics, list_dashboards, etc.) to query that workspace's data. Matching is substring and case-insensitive. When multiple workspaces match the input, all candidates are returned and require disambiguation. Required inputs: - name: workspace name or partial name to search for Output: Array of { workspaceId, name } objects. An empty array means no match.

send_file_to_slack

ChatGPT
Upload a file to Slack on behalf of this workspace's bot. Use this when the user asks to share, export, or send a file (CSV, PDF, spreadsheet, etc.). Encode the file as base64 with the terminal before calling this tool: base64 -w0 /path/to/file | tr -d '\n' Leave channel_id UNSET — the file is delivered to the user's direct message with the bot automatically. Do NOT guess a channel id. Only set channel_id if the user explicitly asks to post into a specific channel the bot is in. Limit: 1 MB decoded. If the file is larger, offer to send a summarised CSV instead or split the content across smaller files. Only available to API-key callers (the Polar Operator Slack gateway). Not available in browser-based Polar sessions.

set_metric_labels

ChatGPT
Adds and/or removes labels on one metric, custom or not. Pass label ids from list_labels. Labels not mentioned are left untouched. Adding a label is safe when the user asked for it. Removing a label changes how people and agents judge the metric, so BEFORE passing remove_label_ids you MUST name the metric and the labels being removed and get a clear affirmation from the user, unless they explicitly asked for that exact removal in this conversation.

update_custom_dimension

ChatGPT
Update an existing custom dimension. CRITICAL (non-self-created items): If you did NOT create this dimension in the current conversation, BEFORE calling this tool you MUST: 1. Call get_custom_dimension_usages with the dimension's id to discover exactly what depends on it. 2. In plain English, tell the user: - the dimension's name (and id, for reference) - what the proposed change is - which downstream objects would be affected — names and types (reports, custom metrics, KI sections), not just counts - that the change will apply immediately to those objects 3. Ask whether to proceed. 4. Once the user gives a clear affirmation ("yes", "go ahead", "do it", "update it", etc.), set userConfirmedInChat: true and call this tool. You do NOT need the user to repeat the id — they already know which item you mean from your previous message. For items you created in the CURRENT conversation: low risk, proceed without the usages lookup. Still set userConfirmedInChat: true (the flag protects the write path regardless). See create_custom_dimension for the full CustomDimension payload shape. Required inputs: - conversation_id - id (the custom_<N> id exactly as surfaced by get_context) - title (new title) - json (per the shape above) - userConfirmedInChat (must be true) On success returns { id, title, editUrl }. The chat UI surfaces the editUrl as an "Open dimension editor" button — do NOT repeat it as a markdown link in your visible response.

update_custom_metric

ChatGPT
Update an existing custom metric. CRITICAL (non-self-created items): If you did NOT create this metric in the current conversation, BEFORE calling this tool you MUST: 1. Call get_custom_metric_usages with the metric's id to discover exactly what depends on it. 2. In plain English, tell the user: - the metric's name (and id, for reference) - what the proposed change is - which downstream objects would be affected — names and types (reports, other custom metrics, KI sections), not just counts - that the change will apply immediately to those objects 3. Ask whether to proceed. 4. Once the user gives a clear affirmation ("yes", "go ahead", "do it", "update it", etc.), set userConfirmedInChat: true and call this tool. You do NOT need the user to repeat the id — they already know which item you mean from your previous message. For items you created in the CURRENT conversation: low risk, proceed without the usages lookup. Still set userConfirmedInChat: true. The server will additionally reject the update if existing reports would become incompatible — that is an extra safeguard, not a substitute for your caution. See create_custom_metric for the full metricData payload shape. Required inputs: - conversation_id - id (the custom_<N> id) - metricData (full replacement CustomMetric payload) - userConfirmedInChat (must be true) On success returns { id, title, editUrl }. The chat UI surfaces the editUrl as an "Open metric editor" button — do NOT repeat it as a markdown link in your visible response.

update_dashboard

ChatGPT
Update an existing dashboard: rename it, replace its blocks, move it to another folder or nav section. A dashboard is a Polar page holding report blocks (tables, charts, metric cards), key-indicator sections and text blocks, optionally grouped into folders. Every block is an OBJECT with a "type". Valid shapes: - Existing report block (keep it as it is): { "type": "report", "id": <number> } - New report block (minted on save): { "type": "report", "json_value": { "title": string, "metrics": string[], "breakdowns"?: string[], "rules": object, "reportMode"?: string, "granularity"?: string } } json_value fields: - granularity: "day", "week", "month", "quarter", "year" or "none". Set it on every chart that shows a trend over time (line, stacked-line, 100-stacked-line, mixed, sparkline-cards, or bars over time): "day" by default, "week" or "month" when the user asks for it or the range spans many months. Without it the block follows the viewer's granularity picker, which defaults to "none" (Entire Range): one aggregate row, so a line chart draws a single point. Set "none" for totals and for charts broken down by a category rather than by date. - Existing key-indicator section: { "type": "kiSection", "id": <number> } - Text block: { "type": "text", "id"?: <number>, "value": "<markdown string>" } - Custom HTML block (a visual no report type can express). Before writing an "html" block, call get_html_block_guide — it documents window.polar, the row shape and worked examples. { "type": "html", "id"?: <number>, "title"?: string, "settings": { "html": "<div id='chart'></div><script>...</script>", "height"?: "auto" | <px> } } - Layout stack: { "type": "horizontal-stack" | "vertical-stack", "id"?: "<string>", "elements": [ ...nested blocks... ] } elements is the dashboard's WHOLE block list, in order, and it REPLACES whatever is on the dashboard today. When updating, pass back the id of every block you want to keep; omit id only for genuinely new blocks. This applies to every block type, text and stacks included: a block sent without its id gets a new one, which breaks anything that points at the old id. A report block sent without an id is also saved as a brand-new report, so an update that drops the ids re-creates every block and strands the originals as orphans. Call get_dashboard_details first to read the current blocks and their ids. Omit elements entirely to leave the blocks untouched and only change the title, folder, page or settings. An html block keeps its markup across updates only if you pass it back with its id; sending the block without one replaces it with a fresh block. When sending elements, expected_version is required: pass the version from your latest get_dashboard_details or update_dashboard result. If the dashboard changed since, nothing is saved: read it again and re-apply your change. To change what an existing block SHOWS, call update_report on that block instead. Replacing it here mints a new report with a new id and drops it from any schedule that referenced it. Required inputs: - conversation_id - dashboard_id On success returns { id, title, deepLink, elements }, where elements is the saved block list in order, each with the type and id it was given — inline reports are minted server-side, so this is where their ids come from.

update_document

ChatGPT
Update a document's title or elements. A document is a title and a grid of elements, filed in folders next to dashboards. Share a written report as a document instead of an HTML report link. Elements: - elements is an array of report element objects, each { type, title, columnSpan, settings }. columnSpan (1 to 4) sets its width on a 4-column grid, 25% to 100%. Two elements with columnSpan 2 each sit side by side: use that for a short comparison, not by default. - Types: text (markdown in settings.value), keyindicator, table, html, and the chart types. - A text element is one small block, as in Notion: one heading, one paragraph or one list. A heading and the paragraph under it are two elements; a heading may share its element only with the single short paragraph directly under it, and only when the two never need to move apart. Tables, charts and key indicators are their own elements. - Send the whole document in one call: every element in elements, however many small blocks it has. - Editing: change only the element that changed. A new paragraph is a new text element at its position, not a rewrite of a larger element. - Elements are static: their numbers never refresh. State the date range they cover in a text element. - Every element is checked when the document is saved: an invalid one is refused with its index and the issues, and nothing is saved. elements replaces every element of the document. Call get_document first, then send the full element list back with your edits applied. Omit elements to only rename it. Use move_document to change its folder. When sending elements, expected_version is required: pass the version from your latest get_document or update_document result. If the document changed since, nothing is saved: read it again and redo your edit on what it returns. Required inputs: - conversation_id - document_id On success returns { id, title, visibility, folder, version, deepLink }.

update_folder

ChatGPT
Folders group dashboards in the Polar left-hand nav. A folder has a key, a title and an ordered list of dashboards. list_folders also returns the built-in "ki" and "report" nav sections and the orphan buckets under those string keys — they are not editable folders, so only numeric folder keys can be created, renamed, deleted or used as a move target. Renames an existing folder or changes where it sits in the nav. Call list_folders first to get the folder key. Required inputs: - conversation_id - folder_key - title

update_report

ChatGPT
Change what an existing report shows: its metrics, breakdowns, filters, display mode or title. The block keeps its id, so every dashboard and schedule that uses it keeps working and picks up the new definition. A report is a saved Polar query block — a table, a chart or a set of metric cards — defined by the metrics it shows, the breakdowns it groups by and the filter rules it applies. A report is a first-class object: it exists in the workspace whether or not a dashboard displays it, and the same report can sit on several dashboards and schedules at once. The report definition is an object: - metrics: string[] — metric keys from get_metrics. At least one; an empty list is refused. - breakdowns: string[] — dimension keys to group by, from list_dimensions. The key "date" groups by time. - rules: object — filters keyed by dimension key, each value a list of { operator, value } clauses. Pass {} for no filter. - metricRules: object — same shape, applied per metric. - reportMode: one of "table", "bar", "line", "stacked-bar", "stacked-line", "100-stacked-bar", "100-stacked-line", "doughnut", "mixed", "metric-cards", "sparkline-cards". Defaults to "table". - granularity: "day", "week", "month", "quarter", "year" or "none". Set it on every chart that shows a trend over time (line, stacked-line, 100-stacked-line, mixed, sparkline-cards, or bars over time): "day" by default, "week" or "month" when the user asks for it or the range spans many months. Without it the block follows the viewer's granularity picker, which defaults to "none" (Entire Range): one aggregate row, so a line chart draws a single point. Set "none" for totals and for charts broken down by a category rather than by date. - top: { topLimit, direction, topMetric } — keeps the leading rows by one metric, which has to be one of metrics. - ordering: { columnKey, direction }, or a list of those. - dateRange: object — pins the report to its own dates instead of the dashboard's. Omit it to follow the dashboard. tab picks the kind, and only that tab's selection is needed: - { tab: "predefined", predefinedSelection }, where predefinedSelection is one of: "today", "thisWeek", "thisMonth", "thisYear", "yesterday", "lastWeek", "lastMonth", "lastYear", "all", "bfcm2019", "bfcm2020", "bfcm2021", "bfcm2022", "bfcm2023", "bfcm2024", "bfcm2025" Rolling windows such as the last 7 or 30 days are not predefined periods; use the relative tab. - { tab: "relative", relativeSelection: { count, granularity } }, where granularity is one of: "day", "week", "month", "quarter", "year", "whole_week", "whole_month", "whole_quarter", "whole_year" { count: 7, granularity: "day" } is the last 7 days; the whole_ variants count complete periods only. - { tab: "range", rangeSelection: { start, end } } — dates as "YYYY-MM-DD". - compareAttributionModels: string[] — up to 2 extra attribution model keys compared side by side with settings.attribution_model. The table shows one column or row per model. - compare, displaySettings, colorSetup, targets — optional presentation settings, best carried over from get_report rather than invented. report REPLACES the whole definition. Call get_report first, change the fields you mean to change, and send the result back — a partial object silently drops the settings you left out. Omit report entirely to rename the report and leave its definition alone. Reshaping an existing block is a job for update_report, not for removing the block and adding a replacement: a re-added block is a different report with a new id, so it drops out of every schedule that referenced the original. A definition with an empty metrics list is refused, and so is a top.topMetric that is not one of metrics. The refusal names the offending field, so read it and send a corrected definition. Required inputs: - conversation_id - report_id On success returns { id, title, deepLink }.

update_views_collection

ChatGPT
Rename a views collection, add or replace views in it, or remove views from it. Views not named in upsert_views or remove_view_ids stay exactly as they are, so their ids keep working everywhere they are used. An upserted view with an id replaces that view whole: a currency or shared_rules left out of it is cleared. Without an id the view is added with a new id. A view is connectors + stores + rules: it limits every report to the chosen stores of each connector, each store optionally narrowed by rules on that connector's dimensions. Each view is { "title": string, "currency"?: string, "shared_rules"?: object, "connectors": [{ "connector_key": string, "stores"?: [{ "identifier": string, "rules"?: [{ "filter_key": string, "operator": string, "value"?: string or number or boolean }] }] }] } Take connector_key, store identifiers and filter_key values from list_views_collections; shared_rules keys are dimension keys from list_dimensions. Omit "stores" to keep every store of the connector. A store without rules keeps all of its rows. Polar Pixel rules are not copied from other connectors: the app editor does that, this tool does not. To filter Polar Pixel the same way as Shopify, add the same rules to the "polar-pixel" connector explicitly. Removing a view breaks every schedule, alert, annotation and role grant that uses it. Without force, removing a view that something uses is refused, and the refusal names what uses it. Removing every view of a collection is refused; use delete_views_collection. expected_version is the collection's version from your latest list_views_collections or write result. If someone changed the collection since, nothing is saved: call list_views_collections again and redo the change on what it returns. CRITICAL: removing views is destructive. BEFORE removing views you MUST: 1. Tell the user, in plain English, which views will be removed and what uses them: the refusal names every schedule, alert, annotation and role grant that uses them. 2. Ask whether to proceed and wait for a clear affirmation ("yes", "go ahead", "delete it"). Never infer consent from the original request. 3. Never pass force: true on the first call. Only retry with force: true after the user confirms what the refusal named. The only exception is a view created earlier in this same conversation that nothing uses. Required inputs: - conversation_id - collection_id - expected_version

generate_report

Claude

get_context

Claude

get_custom_dimension_details

Claude

get_dashboard_details

Claude

get_dimension_values

Claude

get_metrics

Claude

get_view_details

Claude

list_dashboards

Claude

list_dimensions

Claude

rate_report

Claude

Capabilities

Writes

Similar Apps