- Brand
- Polar Analytics
- Category
- Data & Analytics
- Primary Subcategory
- Marketing & Commerce Data Integration
Integration details
Description
Turn ChatGPT into your data analyst. Ask questions about your Shopify store, Meta Ads, Google Ads, and other channels in plain language. Get answers on ROAS, CAC, top-performing SKUs, profit margins, and more, all pulled from Polar’s semantic layer for accuracy. Supports multi-store setups and works with major ecommerce integrations, including TikTok Ads and Klaviyo.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Marketing & Commerce Data Integration
- Secondary Subcategories
- None listed
- Brand
- Polar Analytics
- Access
- Account required
- First tracked
- 2026-06-12
- Tool count
- 61
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your Plugin is no longer discoverable
We previously found Polar Analytics discoverable in ChatGPT, but ChatGPT is no longer surfacing it.Add your email below and we’ll let you know when your Plugin becomes discoverable again, and send you alerts about it.
No spam. Unsubscribe any time.
What discovery looks like

Competing in ChatGPT Marketing & Commerce Data Integration
View Category61 tools agents can invoke
Create a new custom dimension for this workspace. A custom dimension labels rows via when/then/else rules AND can compute derived values with functions (split, concat, regex, date and math operations) in any value block — it is NOT limited to matching discrete values. **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": ValueBlock }, ... ], "else": ValueBlock } 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": ValueBlock, // usually [ "dimension.<table>.<field>" ] "operator": "contains" | "containsCaseSensitive" | "notContains" | "notContainsCaseSensitive" | "startsWith" | "endsWith" | "lower" | "lowerOrEqual" | "greater" | "greaterOrEqual" | "is" | "isAfter" | "isBefore" | "isEmpty" | "isInList" | "isNot" | "isNotEmpty" | "isNotInList" | "isNotNull" | "isNull", "value": "<string>" // for isInList / isNotInList use a // comma-joined string: "US,CA,MX" } ValueBlock = a prefix-notation expression, serialized as a flat array of strings. Each entry is one of: - "operation.<FN>" — a function call; its parameters are the entries that follow, consumed in declaration order. Nest by placing another "operation.<FN>" where a parameter is expected. - "dimension.<table>.<field>" — a dimension reference ("dimension.*.<field>" matches the field on any table). <table> must be a table key from the metric list, and it or one of its extensions must bind <field>; the write is rejected otherwise. - any other string — a literal. Numbers are passed as strings ("1"). A plain label is a single-literal ValueBlock: [ "<label>" ]. Available functions (token(params) -> returns): operation.LEFT(value, length) -> string operation.RIGHT(value, length) -> string operation.LOWER(value) -> string operation.UPPER(value) -> string operation.CONCAT(value1, value2) -> string operation.TRIM(value) -> string operation.REPLACE(subject, pattern, replacement) -> string operation.DATEDIFF(date_part, date1, date2) -> integer operation.DATEFORMAT(date, format_string) -> string operation.DATEADD(date_part, amount, date) -> date operation.DATETRUNC(date_part, date) -> date operation.POW(base, exponent) -> number operation.DIV0(dividend, divisor) -> number operation.MOD(dividend, divisor) -> number operation.ABS(expression) -> number operation.FLOOR(expression) -> integer operation.CEIL(expression) -> integer operation.ROUND(expression) -> integer operation.SPLIT_PART(string, delimiter, part number) -> string operation.DATE(datetime) -> date operation.CONVERT_TIMEZONE(string, datetime) -> datetime operation.CURRENT_TIMESTAMP() -> datetime operation.DAY(date) -> integer operation.DAYOFWEEK(date) -> integer operation.DAYNAME(date) -> string operation.WEEK(date) -> integer operation.MONTH(date) -> integer operation.MONTHNAME(date) -> string operation.QUARTER(date) -> integer operation.YEAR(date) -> integer operation.REGEXP(subject, pattern) -> string Note: operation.REGEXP compiles to Snowflake REGEXP_SUBSTR(subject, pattern, 1, 1, 'i', 1) — case-insensitive, first match, extracting capture group 1. The pattern MUST contain a capturing group (parentheses); without one the function returns NULL for every row. Example — "CC#" = first three dash-delimited parts of a SKU (NK-108-01K4-0 -> NK-108-01K4): { "whenThen": [{ "when": [{ "lineOperator": "", "leftValue": ["dimension.shopify_sales_main.sku"], "operator": "isNotEmpty", "value": "" }], "then": ["operation.REGEXP", "dimension.shopify_sales_main.sku", "^([^-]+-[^-]+-[^-]+)"] }], "else": ["Other"] } Nested functions — CONCAT(SPLIT_PART(sku, "-", 1), "-suffix"): ["operation.CONCAT", "operation.SPLIT_PART", "dimension.shopify_sales_main.sku", "-", "1", "-suffix"] IMPORTANT — availability: at least one when-block leftValue must reference a "dimension.<table>.<field>" entry. Dimension dependencies are collected from when-blocks only; a custom dimension without any never surfaces in the breakdown list. Anchor formula-only dimensions with a guard such as WHEN <field> isNotEmpty THEN <formula>. 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 a new custom metric for this workspace. 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 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.
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.
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
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 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 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 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 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 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
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
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 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 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
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 operation's row fields as documented by describe. notes carry provider-side caveats and must be reflected in your answer when they qualify the numbers. When a row carries a currency field, report figures in that currency, not the workspace reporting currency. Credentials are never exposed here: the request carries identifiers only and the read happens server-side.
Generates a deep-link URL into the app. These deep links open visualizations and configuration screens that are designed for end-user consumption. Required inputs: - conversation_id Supported visualizations: - Tables (default, returned by generate_report; best when breakdowns are present) - Line Charts (with date granularity) - Bar Charts (without date granularity) - Pie Charts (without date granularity) Calling generate_report with the same arguments returns the dataset that the link will display. Required inputs: - 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), - granularity (one of: none, day, week, month, year), - settings (for example attribution model; pass an empty object for non-attribution queries) - ordering (comma-separated key-value pairs of key and direction) - 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`), - reflexion (analysis of the user's goal and the rationale for generating this report). Output: JSON object containing the deep link. The deep link is an opaque URL that must be reproduced verbatim.
Generates a comprehensive analytics report. Required inputs: - metrics (comma-separated keys exactly as received from `get_context`'s `core_metrics` or the `get_metrics` tool), - dimensions (comma-separated keys exactly as received from the `breakdowns` field in list_dimensions), - dateRange (a date expression such as "last 30 days" or "month to date", resolved at each call so a stored call keeps a rolling window) or dateRangeFrom/dateRangeTo (fixed YYYY-MM-DD dates). One of the two is required, - ordering (comma-separated key-value pairs of key and direction) - granularity (one of: none, day, week, month, year), - settings (for example attribution model; pass an empty object for non-attribution queries) - 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). A sensible limit avoids returning more data than the use case needs; the full dataset is available via the returned deep link. For browsing or listing, a single integer between 10 and 25 is typical. At most 5,000 rows are returned inline; a larger limit is accepted. - offsetPage (optional, integer): Page number. Pages are sized to the inline row count, min(limit, 5000), so with limit 20000 and offsetPage 1 rows start at index 5000. - comparisonPeriod (optional): Compare against a previous period, only when the user asks for a comparison. Values: previousPeriod (same duration before current range), previousYear, previousMonth, or range (requires comparisonDateRangeFrom/To). - comparisonDateRange (optional): a date expression for the comparison window, such as "last week - 1 year"; implies comparisonPeriod "range". - comparisonDateRangeFrom/comparisonDateRangeTo (optional, YYYY-MM-DD): Custom comparison date range, only used when comparisonPeriod is "range". - formulas (optional JSON string): variants of workspace metrics computed for this report only, each listed in metrics by its key; see the formulas argument. - conversation_id (optional): The conversation_id from `get_context`. Without one, the call stands alone, so a stored call (for example a dashboard dataset) re-runs for any viewer. Not every metric supports every dimension. Compatibility can be determined either for a set of metrics by passing them to list_dimensions, or for a given dimension by passing it to list_metrics_for_dimension. 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. Large results: when the limit is over 5,000 or tableData holds 5,000 rows, the result also carries `truncated: true` and a `download` object. `download.url` serves the full result of the main period as CSV (up to 100,000 rows) for 1 hour; it grants access to the data, so it is not shared beyond the user who asked. Integrity: `integrity`, when present, carries a per-metric verdict from Polar's daily checks against the source; the states are described in the output schema. Mention a mismatched verdict when reporting that metric. Filters not applied: `filtersNotApplied`, when present, lists metrics left unfiltered by some of the requested rules because their data has no such dimension (for example a campaign filter on a metric with no campaign). Their figures cover every value of those dimensions; say so when reporting them, and never present them as filtered. Metrics with dimension: `metricsWithDimension`, when present, lists requested breakdowns or filters some requested metrics cannot use, with metrics of the same source that carry them. Rerun with those metrics in place of the ones that cannot use it, keeping the requested metrics that can, instead of reporting the breakdown as unavailable. Dimensions not bound: `dimensionsNotBound`, when present, lists metrics whose data lacks the base dimension of the listed custom dimensions. With `scope` "breakdown", those metric rows are not bucketed by the custom dimension: they all sit on the row where it is null; say so when reporting them, and never present that row as one of the dimension's buckets. With `scope` "filter", that custom dimension's filter does not apply to the metric; say its figures are not filtered by it. Metric dates: `metricDateNotes`, when present, gives for some metrics how their values are dated; apply each note when reporting that metric. `metricUnitNotes`, when present, gives for some metrics what each unit counts; apply each note when reporting that metric. `metricCoverage`, when present, lists metrics whose requested range falls outside the days their data covers, with each account's (`datasourceId`) first and last covered day (`from`, `to`; null when the account holds no data); for that account say there is no data before `from` or after `to`, never 0, and never call it a delay, which only get_connector_statuses reports through data_delay and data_available_through. `metricUnits` gives each metric's unit: currency values are in the report currency, `count` is a number of things (orders, refunds, customers), never money. The result also includes dateRange: the resolved analysis window, whose startWeekday, endWeekday and days are computed from the workspace calendar and are authoritative for the window that was queried. When two periods are compared, their days values differ whenever the periods are not the same length, which makes the comparison not like-for-like. attribution_model in settings is relevant for reports involving marketing channel attribution or cross-channel analysis. For Shopify metrics (sales, orders, product data, customer segments), an empty settings object is appropriate when no attribution model is specified. Totals data: a row of server-computed totals for every metric in the metrics list. These totals are authoritative; for grouped or segmented reports the returned rows should not exceed the corresponding server totals (a mismatch indicates the server totals should be used as the source of truth). For ungrouped reports the returned totals can be displayed directly. Percentages and shares are best computed as row_value / total_value rather than from independently summed rows. A missing total for a metric means totals are unavailable for it. The deep link returned by this tool is a user-facing URL that opens the same report in-app; it is the path for the user to view and explore the full dataset beyond the returned rows. The generate_app_report_link tool generates additional links and visualizations. Reports derived from a `get_dashboard_details` element have their own deep links available from that response. After a report, rate_report records a 1-10 quality rating supplied by the user. This rating is the only quality signal Polar receives for reports generated over MCP; collected over time it informs improvements to report relevance and accuracy for the workspace's users. Gating: this tool refuses with `{ error: true, reason, message, book_call_url, connectors_url }` when `account_status` from `get_context` indicates the workspace is not yet activated or has no loaded connector data. reason is one of "needs_activation", "needs_connectors". The refusal applies to the workspace for the remainder of the conversation; the message and URL are the actionable next step for the user.
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.
Detailed per-account connector health from /api/datasources/statuses. Drills into the rollup returned by get_context.connectors when more detail is needed on why a connector is Warning, Broken or Syncing history. 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, data_delay, timezone, effective_timezone, data_integrity, covers }. covers is set only on polar-pixel datasources. 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. The per-check breakdown (name, status, match rate, in/out-of-tolerance days) is available via 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 non-paused accounts, with the exception that an all-paused connector reports paused. A connector or account in warning, broken, or syncing_history state is a data-quality condition relevant to any numbers derived from it. A paused (deactivated) account always reports paused even when it has unresolved errors underneath — the user paused it on purpose, so it does not belong in a "needs attention"/broken rundown. Its issues[] are still listed so the user knows reconnecting won't help until they fix and unpause it. issues[] is a structured array describing the conditions that drive each account's health: - error: { kind: "error", message }: generic plumbing error surfaced verbatim from the connector. - needs_setup: account is connected but no sub-account is selected; setup is incomplete. - missing_scopes: { kind: "missing_scopes", scopes }: the connector is missing OAuth scopes and needs to be reconnected granting these scopes. - delayed: the account's data_delay.status is delayed: its loaded data is older than data_delay.max_age_hours. - drift_summary: { kind: "drift_summary", failed_count, tolerated_count, total_count }: at least one data integrity check has a match rate below the threshold. The data integrity report describes this as "X of Y checks failed". tolerated_count is informational; 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"). For deciding whether a requested period extends beyond Polar's warehoused data, data_available_through is the data-delay boundary: synced data has been loaded into the warehouse up to this time, in the account's timezone. polar-pixel has two datasources, each naming the metrics it backs in covers: follow covers to know which metrics a delay affects. Attribution & journeys has a UTC data_available_through, and its data_delay age_hours is how far it lags behind the latest Shopify order, not the time elapsed. Polar Pixel's Events & sessions datasource has no data boundary; don't infer a delay for it. It says nothing about completeness or accuracy before that time: a failed or partial load earlier can leave gaps. data_integrity and get_data_integrity_report cover accuracy. effective_timezone is the account timezone whose day ends last. last_completed_sync_at is an operational sync timestamp, not a data-delay boundary, and is not a substitute. For connector health and delay warnings, explicit issues[] entries remain the canonical source. data_delay is { status, age_hours, max_age_hours }, the account's data-delay verdict, or null when it is not evaluated. status is on_time or delayed. age_hours is the hours elapsed since data_available_through. When data_available_through is null because the only loaded syncs had no end date (an unbounded first import), age_hours counts from when that sync's data loaded instead. When a sync completed but none of its data has loaded yet, the account reports syncing_history until its first completed fetch is older than max_age_hours; after that it is delayed and age_hours counts from that fetch. age_hours is null only when that fetch time is unknown, and then status is delayed. max_age_hours is the age the account's data may reach before it counts as delayed: 3 on an hourly-refresh plan, 27 on a daily plan, unless the connector sets its own. null means delay is not evaluated for this account: custom connectors, connectors still on the legacy pipeline, paused accounts, accounts syncing history, and accounts that never completed a sync. Like data_available_through, it measures delay only and says nothing about completeness or accuracy. Deep-link pattern (account names are not slugified): /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 plus per-check drill-down) 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.
Prepares the analytics environment for a conversation and returns the workspace context required by every other tool. It returns a combined JSON object with the following elements: conversation_id: Conversation identifier consumed verbatim by the other tools in this session. context: Workspace context { currency, timezone, brand_name } (brand_name defaults to the email of the first registered user when no company name is set: often the brand's domain, sometimes a personal email). account_status: { activation, data, book_call_url, connectors_url, message } describing whether the workspace is activated and whether its data is loaded. activation is one of 'pending_call', 'call_booked', 'activated'. data is one of 'none', 'loading', 'ready'. When activation is not 'activated', booking the setup call at book_call_url is the prerequisite next step and data tools will refuse the request. Otherwise, when data is not 'ready', connecting data sources at connectors_url is the prerequisite. The 'message' field is a ready-to-deliver explanation that pairs with the relevant URL. connectors: Canonical per-connector rollup. Each entry { key, health, connections } where health is one of healthy, syncing_history, warning, broken, paused, pending (worst across non-paused accounts; an all-paused connector reports paused). connections is the per-status breakdown of this connector's accounts and only includes health states with at least one account (absent keys mean zero). Total account count equals the sum of connections values. get_connector_statuses drills into specific connectors and exposes the structured issues[] used to compose remediation prose. A connector in warning, broken, or syncing_history state is a data-quality condition relevant to any numbers derived from it, and its connector page has a user-facing deep link for review. A paused account always reports paused even with unresolved errors underneath: treat it as paused, not as needing action — keep it out of any broken/"needs attention" rundown. polar-pixel appears in this list like any other connector: health syncing_history means it is still warming up, healthy means it has matured. The canonical health currently surfaces only 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. last_completed_sync_at on its own is not a reliable freshness signal; explicit issues[] entries are the canonical source. custom_connectors: Array of { key } for connectors that have queryable data tables but no live monitoring (custom imports, data warehouses, legacy entries). Health is unknown for these. connector_status: DEPRECATED, superseded by 'connectors' and 'custom_connectors'. Kept only for backward compatibility and will be removed in a future MCP version. 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. A polar-pixel legacy entry with is_warmed_up=false signals the pixel needs attention: when installed_at is a date, the pixel is still warming up since installed_at and conversion / attribution numbers are not yet statistically meaningful (the calibration window depends on traffic volume and industry but is typically at least a couple of weeks); when installed_at is null, the pipeline last produced data on polar-pixel 'until' and may not be currently flowing. Deep-link pattern for the Connectors area (use canonical 'connectors' key and accounts[].datasource_id from get_connector_statuses; account names are not slugified): 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 workspace's custom dimensions: the 50 most recently created or updated. Each entry carries { id, label } plus ONE of: - summary: human-readable one-line purpose (set when authored or AI-summarized) - refs: array of underlying dimension field names the formula touches (for example ["billing_country"]); a structural hint when no summary exists (a label like "geo v2 jamie" with refs ["billing_country"] is typically a country grouping). Label, summary and refs together describe whether an existing dimension covers a given concept. The full whenThen formula is not inlined; `get_custom_dimension_details(id)` returns it for a single candidate. The `list_custom_dimensions(q?, offset?, limit?)` tool paginates beyond this initial page and searches by case-insensitive substring on title and summary. views: Views list as an array of { id, title }. labels: Present only when the workspace has enabled metric labels: the full label dictionary as an array of { id, name, category, description, metric_count }. Custom 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. Use this dictionary to understand what each label on a custom metric (from get_metrics / list_custom_metrics / get_custom_metric_details) means before choosing between metrics. snowflake_database: Present only when the `query_snowflake` tool is available for this workspace: the workspace's own Snowflake database name, already quoted and ready to interpolate into fully-qualified identifiers (there is no session default database, so every table reference needs it). Required input: - initialQuestion (user's initial prompt, stated exactly as received) - version (the MCP client version; current is 4.3) The response may include a 'warning' field describing an outdated client version or other notice intended for the user.
Returns the full definition of a single custom dimension (rules and connector filters). The compact entry in `get_context.custom_dimensions` is usually enough to identify a dimension by intent; this tool returns the full body when the formula itself is needed. Required inputs: - conversation_id - id (the id of a custom dimension, exactly as obtained from the `custom_dimensions` field of `get_context`)
Lists every workspace-owned object that currently depends on a given custom dimension: reports, custom metrics, and KI sections. Updating or deleting a dimension that was not created in the current conversation can affect these objects; the usage list is the canonical source for the human-readable impact summary that should precede confirmation of such a change. Returns { usages: [{ id, title, type }] }. type is one of "report", "custom_metric", "ki_section". An empty list means nothing currently depends on the dimension. Required inputs: - conversation_id - id (the custom_<N> id of the dimension)
Returns the full formula of a single custom metric by id, including elements and formatting. The compact entry from `list_custom_metrics` or `get_metrics` identifies the metric; this tool returns the formula body needed to inspect or extend it. Required inputs: - conversation_id - id (the custom_<N> id) Returns { id, label, description?, elements, formatting }.
Lists every workspace-owned object that currently depends on a given custom metric: reports, other custom metrics, and KI sections. Updating or deleting a metric that was not created in the current conversation can affect these objects; the usage list is the canonical source for the human-readable impact summary that should precede confirmation of such a change. Returns { usages: [{ id, title, type }] }. type is one of "report", "custom_metric", "ki_section". An empty list means nothing currently depends on the metric. Required inputs: - conversation_id - id (the custom_<N> id of the metric)
Returns the full definition of a single dashboard, including all of its elements. Customers generally refer to dashboards and elements by their titles. Required inputs: - conversation_id Output: A single dashboard with the details of its elements, along with deep links. The dashboard and each report element carries a user-facing deep link to its in-app view. The table data has a limit of 50 applied; more rows can be retrieved with the `generate_report` tool. If the workspace is not yet activated or has no connector data loaded, the response contains 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`. An html element carries `runtimeErrors` and `runtimeErrorsLastSeenAt` when browsers reported errors for that exact html in the last 24 hours; locations read "html line N:C" of the block's html. When the user says a block isn't working, read the dashboard first. Editing the html clears them; the new html has no errors until a browser reports some. An element whose data failed to load carries a `data_error` message, and `data: null` when nothing could be fetched at all. Never present such an element's numbers as fact — report the other elements normally and tell the customer which ones failed. Element data loads within a 55s budget. An element still loading at that point carries `data_not_loaded: true`, `data: null` and a `next_step`: load it with `generate_report` from the element's own metrics, breakdowns, rules, metricRules and settings, and do not call `get_dashboard_details` again for it.
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, and provides the drill-down for a non-null `data_integrity` rollup returned by get_connector_statuses. Returns { tests: [{ name, status, errorRatio, matchedValues, unmatchedValues }] }. Each test is a "check" on the report. A check's status is driven by its overall match rate, not by individual days. match rate equals (1 - errorRatio) and is surfaced as a percentage (for example errorRatio 0.109 corresponds to "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. The report surface uses the phrasing "match rate". 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 (for example an 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. errorRatio is the magnitude of mismatch as a fraction of expected (0 means 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 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 { dimensions: [...] } with the possible values for the dimension, up to a maximum of 500 values.
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.
Returns the metrics available for a list of sources. Optional `q` finds a concept across every metric and breakdown of the listed sources: it matches labels, keys, descriptions, custom table titles and common commerce synonyms (subscriber ~ selling plan, budget ~ target), as well as substrings of key, label and summary. Matching metrics come back best first; `dimensions` lists matching breakdowns, each with a metric key it applies to, so a concept held only in a breakdown is found too. With q, at most 40 metrics and 20 breakdowns are returned; `note` says when more matched. Separate distinct concepts with commas; a phrase stays one term. Connector metrics are returned as { key, label }; a computed connector metric also carries formula, how this workspace computes it, written with metric keys (discounts and refunds are stored as negative amounts, so a + on them deducts them). Custom metrics carry { key, label } plus ONE of: - summary: human-readable one-line purpose (set when authored or AI-summarized) - refs: array of underlying metric labels the formula touches; a structural hint when no summary exists (a label like "ROAS v2 jamie" with refs ["Total ad spend", "Net sales"] is typically a return-on-ad-spend metric). Label, summary and refs together describe whether an existing custom metric covers a given concept. 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. The 'key' values are opaque identifiers consumed verbatim by other tools. The full elements + filters formula is not inlined; `get_custom_metric_details(id)` returns the full body for a single candidate. The custom-metrics list is capped at the 300 most recently created/updated custom metrics. The `list_custom_metrics(q?, offset?, limit?)` tool paginates the full set and searches by case-insensitive substring on title and summary. SOURCES: A source is either a connector name (from `connector_status`) or 'custom-metrics'. Including 'custom-metrics' in the sourceList returns custom metrics for the workspace. Custom metrics contain user-defined business logic that connector metrics do not capture. A sourceList that omits 'custom-metrics' returns connector metrics only, so a report built from that result does not reflect the workspace's user-defined metrics. SOURCE-TO-CONNECTOR MAPPING: Common product names map to canonical connector keys: - "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` The `name` field in `connector_status` is the authoritative source; matching is case-insensitive. The result is scoped to the sources in sourceList; a source that is not listed contributes no metrics, so a sourceList narrower than the question's scope yields an incomplete metric set. A question spanning multiple sources is covered by listing every referenced connector alongside 'custom-metrics'. EXAMPLES: - "google analytics four landing page data" → sourceList: "google-analytics-four,custom-metrics" - "Shopify revenue last 30 days" → sourceList: "shopify,custom-metrics" - "blended ROAS this month" → sourceList: every ad-platform connector + shopify + custom-metrics - "how is my business doing" → sourceList: every connector in `connector_status` + custom-metrics Custom metrics (keys starting with 'custom_') encode user-defined business logic specific to this account; when a matching custom metric exists, it takes precedence over the equivalent standard metric. 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 (for example `polar_pixel_conversion_rate`, `blended_roas`, `pixel_cac`). These are workspace-level metrics that combine data from multiple integrations/tables. - Custom metrics start with `custom_` prefix. Metric keys are opaque strings returned by this tool and consumed verbatim by other tools; the patterns above describe naming conventions, not a generative grammar.
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
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 by type and id. Call get_report for a report block's content. Required inputs: - conversation_id - version_id
Returns the full definition of a view (rules and connector filters). A view is a segment or set of dimensions pre-configured by the user. The compact entry in `get_context.views` is usually enough to identify a view by title; this tool returns the rules and connector filters when those details matter. Required inputs: - conversation_id - view (the id of a view, exactly as obtained from the `view` field of `get_context`)
Browses or searches the workspace's custom dimensions beyond the first page surfaced by `get_context`. Sorted by recency-of-update (most recently edited or created first). The `q` parameter filters by case-insensitive substring on title or summary; omitting `q` pages through everything. Searching by an underlying field name (for example q="country") surfaces every dimension whose label or summary references that field. Returns { items: [{ id, label, summary?, refs? }], total, offset, limit, hasMore }. Each item carries the same compact shape as get_context's custom_dimensions slot. The `get_custom_dimension_details(id)` tool returns the full whenThen formula for a single candidate. Required inputs: - conversation_id Optional inputs: - q (case-insensitive substring; matches title or summary) - offset (default 0) - limit (default 50, max 200)
Browses or searches the workspace's custom metrics. Sorted by recency-of-update (most recently edited or created first). The `q` parameter filters by case-insensitive substring on title or description; omitting `q` pages through everything. 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 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)
Returns the list of dashboards configured in the workspace. 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 dashboards and elements by their titles. Further details on a specific dashboard can be retrieved with get_dashboard_details. Required inputs: - conversation_id Output: A list of dashboards and their elements, along with deep links. Each dashboard and report element carries a user-facing deep link to its in-app view.
Given comma-separated metric keys from get_context, returns the breakdowns, filters, and settings (such as attribution_model) compatible with those metrics. Compatible dimensions vary by metric set. All metrics support the 'date' dimension implicitly. other_dimensions groups breakdowns of the same source these metrics can't use, with the metrics that can; switch to those metrics to break down by them. Omitting the metrics argument instead returns the full catalog of the workspace's groupable dimensions as { key, label } pairs. Name-based dimensions are preferred over their id-based counterparts (for example sales_channel_name rather than sales_channel). get_dimension_values returns the possible values for a dimension. Custom dimensions (keys starting with 'custom_') hold account-specific logic and take precedence over the equivalent standard dimension, except custom_default.
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
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).
Given a dimension key, returns the metrics that support it as a breakdown. When generate_report is called with this breakdown, including any metric not in this list will cause the breakdown to fail with "cannot apply breakdown by <dimension>". Computed metrics from the same table are not interchangeable with raw metrics for this purpose. Required inputs: - conversation_id - dimension (a single dimension key from list_dimensions) Returns an array of { key, label } metrics. Empty when the dimension does not exist or no metrics support it. Coverage: this tool does not include blended metrics (no table prefix), custom metrics, or custom dimensions (keys starting with custom_). Compatibility for those can be verified via list_dimensions.
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
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). 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
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 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 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
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.
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, 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
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 or by an end. BASE PERIODS today, yesterday, tomorrow, 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 all time 1970-01-01, before any data, to today ENDS (to, through, until) yesterday to today from the start of the first period to the end of the second 2026-07-01 to today 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.
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
Polar Analytics ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Polar Analytics's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Polar Analytics alternatives on ChatGPT?
As of 2026-11-02, Polar Analytics competes with Adzviser, Adzviser Amazon Ads, Adzviser CallRail, Adzviser CallTrackingMetrics, Adzviser Klaviyo, Adzviser Snapchat Ads, Catchr, Converly and 15 more in ChatGPT Marketing & Commerce Data Integration, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.