MCP App Store
by KeenEthics
Business & Operations
Kreel icon

Kreel

by Beyond Peaks Ltd

Overview

Kreel connects ChatGPT to normalized ecommerce performance data from Shopify, Klaviyo, Meta Ads, and Google Ads. Users ask about revenue, ROAS, campaigns, creatives, email performance, and anomalies across their connected brands, keep a durable brand knowledge base, and publish hosted reports on request.

Tools

archive_experiment

ChatGPT
Soft-archive a decided experiment. Its result and learning remain available.

archive_learning

ChatGPT
Soft-archive a learning (drops it from the active set and the cap count). Args: brand: Brand name or id. Required when you can reach more than one brand.

compare

ChatGPT
Compare a metric across subjects, optionally against a derived period. A single subject is supported when comparison is supplied. Every subject carries revenue_kind, matching get_performance's vocabulary: store_net (a Shopify account subject — refund-correct NET store revenue, so it reconciles digit-for-digit with get_performance), platform_attributed (an ad/email account or any campaign subject — provider-reported value under its own attribution model and click/view windows, not necessarily last-click; equal revenue kinds do not imply equal attribution settings). A platform='all' account subject is store_net too: store-net revenue, and for roas MER (store net / total Meta+Google+TikTok ad spend, the same figure as get_performance's summary.mer), carrying basis: "mer". When metric is revenue, roas, orders or cpa and the subjects mix revenue kinds, a top-level notices entry flags that the numbers are not directly comparable. Args: brand: Brand name or id to query. Required when you can reach more than one brand; optional (auto-selected) when you can reach exactly one. Call list_brands to see options. account: Optional Connected Account or list of them (each a display name, platform account id, Kreel id, or a saved Account Group name, which resolves to its member set; several values union) applied to EVERY subject, so subjects stay comparable. Omit it to compare across the blend of all the brand's accounts. An account the brand does not hold returns {"error": "unknown_account", "available_accounts": [...]}. subjects: list of {"label", "platform", "entity_type", "id"?}. entity_type = "account" compares full channels; "campaign" requires an "id" (platform_campaign_id). "email_campaign" and "flow" accept Klaviyo ids, optional account_id and send_channel (email/sms), with platform "klaviyo" or omitted. Rates are unique/delivered fractions. Campaign cohort comparison is group-analysis with source_type=campaign. subject.platform is optional and defaults to 'all': for an account subject, the whole brand on one store basis, not a sum of channels. For platform-reported revenue, ROAS or conversions, name the platform. Campaign subjects support 'meta', 'google', 'tiktok', or 'all'; 'all' scans every paid-ad platform. Campaign lookup scans only the top ~1000-by-spend campaigns and matches by id; set the subject's platform to avoid cross-platform id collisions, and 'no data' may mean truncated rather than absent. GA4 subjects (platform "ga4", never mixed with other platforms) are the website or one segment: entity_type "website", or "channel_group" | "traffic_source" (source / medium) | "device" | "landing_page" with id = that value, plus optional GA4 filters (channel_group, source_medium, device_category, landing_page). GA4 metrics: sessions | engaged_sessions | engagement_rate | purchase_session_rate | ecommerce_purchases; each subject lists its GA4 warning codes and a comparison is withheld unless stored GA4 data covers the whole comparison window. comparison: Optional {"mode": "previous" | "year" | "custom", "start"?, "end"?}. Derives period-over-period values with strict coverage. delta_pct is (current - previous) / previous, rounded to 4 places (0.1 means 10%). metric: spend | revenue | roas | cpa | orders | conversions | ctr | delivered | open_rate | click_rate | revenue_per_recipient. For account subjects, orders and the denominator of cpa use Shopify store orders for Shopify and platform conversions for Meta, Google, TikTok, and Klaviyo. A platform='all' account subject uses Shopify store orders, and its cpa is total paid-ad spend / store orders (basis: "ad_spend_per_store_order"); its conversions is value: null with a reason, because platform conversions overlap and are never summed across platforms. A platform='all' subject with no Shopify store in the account scope returns value: null with a reason for revenue, roas, orders and cpa. For campaign subjects, orders equals conversions (no true order count exists at campaign level). window: {"days": 7} OR {"start": "YYYY-MM-DD", …

create_dashboard

ChatGPT
Create a custom dashboard of DSL tiles on the Kreel Dashboards page. The dashboard is stored in Kreel and only Kreel members can see it; nothing is published outside the Workspace or sent to another service. A tile is {kind, title, dsl, layout: {x, y, w, h}, display?: {metric?, format?}} on a 12-column grid (x + w <= 12). kind is: - kpi: draws the first row's display.metric; group or filter the query to one row (e.g. group_by ["channel"] with a channel filter), no date grouping; - timeseries: selects and groups by the table's date column, plus at most one series column; - bar: groups by exactly one non-date column; - table: any query, shown as rows. dsl is the query tool's DSL (validated the same way, no cursor); the dashboard range replaces any date filter it has. A tile that sums store and platform-attributed revenue together — the #289 mixed-basis notice query attaches — is rejected, and nothing is saved. At most 24 tiles. Needs the write scope and the member role. Private dashboards are yours; brand shares one with the Brand's Kreel members and workspace makes a template for every Brand of your Workspace. Args: name: Dashboard name (at most 120 characters). tiles: The tiles, in reading order. brand: Brand name or id. Required when you can reach more than one brand. visibility: "private" (default) | "brand" | "workspace". range: {preset: yesterday|7d|30d|90d|mtd|custom, days, start_date?, end_date?}; default the last 30 days. description: Optional one-line description.

detect_anomalies

ChatGPT
Flag channel and campaign metric changes with structured evidence. Inventory stockout_risk and dead_stock use canonical products and trailing 28-day velocity. Linked ACTIVE Meta spend is best-effort, Meta only and tagger-dependent; inspect inventory_metadata.advertising_coverage for partial coverage. Klaviyo campaign bounce/spam rates compare with preceding account sends: at least 1,000 deliveries and 5 bounces / 3 complaints, +0.005 / +0.0005 absolute rate increases, and 25% / 50% relative warn / critical increases. Measured zero-baseline spikes are new_activity; flow gaps are omitted. Also detects product/variant high_return_rate against the recent store baseline (30 sold, 5 refunded, 100 store units; +5pp and 2x warn/3x critical). Weekly overdue_share (shopify_store, whole brand only, not with account): the latest weekly RFM run's overdue customers / scored customers against the run exactly one week earlier. Warn above a 20% relative rise, critical above 40%; no alert when either week is missing or the prior share is 0. Evidence has basis "weekly_snapshot", week_start and prior_week_start. Linked active Meta spend is canonical-product evidence over the last seven completed days, separate from the return window. The tool evaluates spend, revenue, orders, ROAS, CPA, CTR, and CPM. Additive metrics (spend, revenue, orders) are compared as calendar-day averages; ratios (roas, cpa, ctr, cpm) are recomputed from summed bases, never averaged. Each alert's evidence carries `basis: "daily_avg" for the additive metrics or "ratio" for the ratio metrics, alongside the window_value/baseline_value keys (unchanged). Channel orders means store orders for Shopify and platform conversions for attributed channels; campaign orders also means platform conversions. Google values may be fractional. Results are capped at the 10 highest-severity alerts and truncated says whether more matched. Default warn / critical percent thresholds are: spend 25/50, revenue 20/40, orders 20/40, ROAS 20/40, CPA 25/50, CTR 20/40, and CPM 25/50. Direction is literal up or down, not a claim that a movement is good or bad. A zero baseline is only flagged for spend at 50 account-currency units per day or orders at 5 per day. It is new_activity when the recent value crosses that floor and stopped when a baseline above the floor falls to zero. New activity has null pct_change; stopped activity has -100. Twice the floor is critical. Undefined ratios and other zero-baseline metrics are skipped. Channel revenue is refund-correct store net for Shopify and platform-attributed value elsewhere. Campaign revenue is platform-attributed. Attribution models and click/view windows differ by provider; the platform_attributed label does not imply last-click attribution. Revenue and ROAS evidence carries revenue_kind. Campaign evaluation is bounded to the top 20 by baseline spend, plus bounded current-only launches. Args: metrics: Optional metric allowlist, including days_of_cover, velocity and overdue_share. brand: Brand name or id to query. Required when more than one brand is reachable; optional when exactly one is reachable. Call list_brands to see options. account: Optional Connected Account or list of them (each a display name, platform account id, Kreel id, or a saved Account Group name, which resolves to its member set; several values union). Omit it to detect anomalies over the blend of every account; name one to detect them within that account alone, which is how a swing in one market stops being averaged away by the others. A brand with several accounts on one platform gets an accounts manifest naming what was folded in. An account the brand does not hold returns {"error": "unknown_account", "available_accounts": [...]}. window: Recent period, default {"days": 7}. baseline_window: Comparison period. Defaults to the 14 days immediately before window`. threshold: Optional backwards-compatible extra minimum percent change. It can make detection stricter but cannot lower the metric table. channels: Exact chann…

get_agent_run

ChatGPT
One run of a scheduled agent: status and reason, attempts, duration, inputs, the steps it ran, its output summary, the Report it published and what each destination did. A held draft includes its rendered document.

get_brand_context

ChatGPT
Return identity, operational metadata, and the saved brand profile. Top-level fields: id, name, currency, timezone, revenue_basis, sales_channels, is_active, onboarded, and connections (each connected platform with account name, active flag, and whether it last errored) — the context needed to interpret figures correctly (currency on revenue, timezone on date boundaries, which platforms have data). timezone is the resolved reporting timezone (the Shopify shop's own timezone while Shopify is connected, else the brand profile's saved timezone, else UTC) - the same value readiness.expected_data_through and every daily table's day boundaries are computed from. It is not the raw, unbackfilled profile setting. connections includes only currently-active platforms; disconnected ones are omitted (not shown with is_active=false), so per-connection is_active is always true. account_groups lists the Brand's saved Account Groups — named cross-platform sets of Connected Accounts ("EU", "US") — as {name, id, member_count, platforms}. A group name is a valid account argument on every read tool and resolves to its member set; the result's accounts manifest then names the group. Empty when the brand has none. Each connection carries freshness signals: data_through is the latest activity date represented in queryable rows, materialized_at is when those query rows were last rebuilt, materialized_from and materialized_through identify the rebuilt date window, and last_synced_at is when the last completed performance-relevant Sync Run finished. These can differ when a sync and materialization find no newer activity. data_through is measured from a single representative channel per platform (Shopify = shopify_store), not every channel that platform may feed. With several Connected Accounts on a platform each is judged on its own runs and the worst is reported: last_synced_at is the least recently synced account's (null while any account has never completed a run), and last_sync_status is the latest performance-relevant Sync Run's status of the worst account, including failures, so a sibling's later success never hides one. last_sync_partial is true when that run completed but recorded slice errors. materialized_at advances after a successful full materialization pass even when sparse activity produces no query rows. sources is the per-source freshness verdict the dashboard shows in its report header, one entry per connected source (plus disconnected sources whose retained data still feeds reports): platform, label, data_through, last_success_at (latest fully successful performance sync, UTC), sla_hours, status (fresh | stale | failed | reauth, worst first: reauth > failed > stale > fresh; or disconnected for retained data from a source with no active connection, which is neither fresh nor a fault) and reason (sync_failed, sync_partial, past_sla, never_synced, reauth_required, token_expired, needs_selection, token_error, disconnected; demo on a demo Brand's seeded sources; otherwise null when fresh). Quote these when stating freshness so your answer matches the dashboard. readiness is the mandatory preflight for performance analysis. Call this tool before get_performance, then disclose every warning before interpreting rows. ready is false when there are no connections or a connection has missing data, source data not synced or query data not materialized within freshness_target_hours, the materialized window does not cover the documented 30-day analysis range, a failed or partial latest sync, or a connection error. Sparse platform activity is not considered stale when both the source sync and query surface refresh are within the freshness target. expected_data_through is yesterday in the Brand's reporting timezone. Machine-readable warning codes are no_connections, missing_data, stale_data, sync_failed, sync_partial, and connection_error. data_coverage reports the queryable historical window at reporting-table, platform, and day/month grai…

get_budget_pacing

ChatGPT
Ad spend pacing against the monthly budget plan and the platform budgets. The month is the Brand-calendar month containing as_of (default: yesterday). Read-only: every recommendation is advisory and Kreel never changes a budget. platforms - one entry per ad platform (Meta, Google, TikTok) against its line of the active budget plan (spend_meta / spend_google / spend_tiktok, see get_plan), in the Base currency with spend converted per row date: mtd_spend, plan, expected_to_date (the linear pace line, plan x days elapsed / days in month), pace_pct, projected month-end, deviation_pct, required_daily_spend and daily_change_needed (the run-rate change that lands on plan). The projection method is always "Linear from trailing 7-day average": MTD + the average of the seven days ending as_of x days left. status is on_pace, over_pace or under_pace (projected beyond the Brand's tolerance_pct, default 10, strictly), no_plan (blank plan cell: create one) or not_scored with reason (a failed, stale or backfilling source, missing FX, a plan in another currency, or an account lens that covers only part of the platform). Accounts under a platform are contributions with share_of_plan_pct, never per-account plans. entities - each active budget owner (a campaign with its own budget, otherwise its budget-bearing ad sets / ad groups; a Google shared budget once across its member_campaign_ids) in the account's own currency. Daily budgets carry yesterday_pct and trailing_7d_pct; flag is under_delivering below 70% or overdelivering above 100% (exactly 70 and 100 do not flag). Lifetime budgets carry budget_remaining (reported or derived), days_remaining to schedule_end and projected_vs_remaining_pct. Budgets are current snapshots (snapshot_at), so an as_of other than yesterday returns no entities (entity_status: historical_anchor). An unavailable entity names its unavailable_reason. Args: as_of: A completed day "YYYY-MM-DD" on the Brand calendar; default yesterday. A later day is an error. account: Optional Connected Account(s) or Account Group, as in get_performance; an unknown account returns {"error": "unknown_account", "available_accounts": [...]}. brand: Brand name or id. Required when more than one brand is reachable.

get_creatives

ChatGPT
Fetch creative performance for Meta ads, TikTok ads or Google assets. Meta-only naming: naming contains strictly parsed, case-sensitive values (UGC and ugc differ), or null. Configured fields support filters/group_by; missing values form an unmatched bucket. naming_matched filters coverage. rank_by ranks rollups by a chosen metric; min_spend sets a Base-currency spend floor. Incomplete/undefined groups are excluded and counted; cost metrics rank ascending, other metrics descending. limit caps ranked groups. Google has no naming schema, filters, or group_by support. This is the tool for creative analysis: each creative carries its delivery performance over a window (spend/impressions/roas/ctr/cpa…) plus fatigue diagnostics and Kreel's AI creative tags, so you can answer "which hooks/formats convert" and "what's fatiguing" in one call without joining tables yourself. ⚠️ Media URLs and ad copy require `fields="all"`. The default fields="core" is token-safe and returns NO media/copy and NO Gemini prose — see fields below. Canonical Meta hook_rate = thumbstop = video_3_sec_views / impressions; thruplay_hold_rate = thruplays / video_3_sec_views (15 seconds or completion). These are four-decimal fractions, null for missing inputs or nonpositive denominators. Asset grouping uses confirmed image hashes only, falling back to account-scoped creative identity, then ad identity. Legacy fatigue uses 30-second video_views for hook and p100 / video_views for hold. Google v1 is metrics-only at asset + field-type grain. It covers RSA and Demand Gen assets through ad_group_ad_asset_view, and PMax assets through asset_group_asset. Rows include asset id, type, name/text when Google supplies it, field_type, performance_label, impressions, clicks, ctr, conversions, conversion_value, and cost. The envelope and each row echo the selected Google customer id so asset identities stay account-scoped. Google v1 has no Gemini tags, media URLs, fatigue, filters, or group_by. Pass fields="core"; tags and all return an explicit not-available error rather than silently degrading. When the Google page comes back empty, the envelope adds an empty_reason so a zero result is never ambiguous: "asset_metrics_not_synced" (the per-asset sync has never populated this account - no rows for the selected customer on any date, e.g. the pipeline hasn't run or a shared-quota hit kept coming up empty) vs "no_rows_in_window" (rows exist for the account, just none delivered in the requested window - widen date_range). TikTok is ad-grain creative parity with Meta (#642 D4): each delivered TikTok ad that has a fetchable asset is one row carrying creative_id (its video_id, else first image_id - reuse across ads shares one id and one stored copy), asset_kind, media_type, the shared hook_type / format_type tags, intelligence (fields="tags"), Kreel-signed image_url / video_url plus body / call_to_action / landing_page_url (fields="all"), and spend / impressions / clicks / purchases (website purchases) / revenue (their TikTok-attributed value) / cta_conversions / vta_conversions with derived roas / cpa / ctr / cpm / cpc / conversion_rate and a per-row attribution block, exactly as get_performance(platform= "tiktok", entity_type="ad") reports them; a semantics block restates the definitions. Delivered ads with NO fetchable asset (Spark Ads without a video_id, LIVE_CONTENT, CATALOG_CAROUSEL) are excluded from the rows and counted, with their spend, in without_creative - total_creative_spend covers only the rows a creative ranking can see. Filters: ad_id, campaign_id, adset_id (= adgroup_id), status (the raw TikTok operation_status switch, e.g. "ENABLE" - advertiser-enabled, NOT guaranteed delivery). Each row carries both status (that switch) and secondary_status (TikTok's effective delivery state, as get_entities reports it): an ENABLE ad under a paused campaign / ad group reads status "ENABLE", secondary_status "AD_STATUS_CAMPAIGN_DISABLE" / "AD_STATUS_ADGROUP_DISABLE" and lifecycle_sta…

get_daily_brief

ChatGPT
Return a deterministic, source-cited one-day brief on the Brand reporting calendar. Args: brand: Brand name or id to query. Required when the caller can reach more than one Brand. date: Optional YYYY-MM-DD Brand-local day. It must be before today. Omit it for yesterday. Each metric compares the day with the same weekday a week earlier and with the preceding 28 complete days (D-28 through D-1): money and counts against the 28-day total / 28 (`daily_average), ratios against aggregate numerator / denominator (aggregate_ratio). A comparison whose history is incomplete is null and comparison gives the reason; it is never zero. profit has yesterday and month to date: refund-correct store net revenue, orders, AOV, ad spend, MER, CM1 and contribution after ads. Margin amounts use current unit costs, exclude tax and shipping, and are reported only at 80% cost coverage or more; otherwise the level carries its status and coverage and no amount. pacing and budget_pacing are month to date through the day. findings are the top five from the findings registry. freshness.stale flags a platform more than 3 days behind or unknown. edition.status is pending` until that day's materialization has completed; delivered digests wait for it. Store and attributed revenue are different bases and must not be added.

get_dashboard

ChatGPT
Read one dashboard with every tile's numbers — what the Dashboards page shows. Returns dashboard (name, visibility, range and tiles), the resolved window on the brand's reporting calendar, and tiles[]: per tile its dsl as run (the tile's DSL with the dashboard range on the date column; passing it to query returns the same rows), applied / not_applied, and result ({meta, notices, rows} as query returns) or error. Args: dashboard_id: The dashboard id (from list_dashboards or a dashboard link). brand: Brand name or id. Required when you can reach more than one brand. A workspace template runs against this brand. range: Optional range for this read only, e.g. {"preset": "7d", "days": 7} or {"preset": "custom", "days": 14, "start_date": "2026-09-01", "end_date": "2026-09-14"}. Omit for the dashboard's saved range.

get_decisions

ChatGPT
Read one decision (with its frozen payload) or a bounded page of summaries. status_group: open (proposed), executed (executed, verified, reviewed_no_verdict) or closed (rejected, withdrawn, expired). A rejected decision carries the human's rejection_reason; do not re-propose it unchanged. A proposal past its expiry reads as expired. Verdicts land within about 15 minutes of a decision's review date; until then an executed decision past its review date reads as executed.

get_deliverability

ChatGPT
Compare provider engagement, observed-attempt bounce rates and complaints. Defaults to 28 completed days versus the preceding 28. Rates and deltas are fractions. Includes daily counts, period bounce mix, stored message/flow evidence, campaign send-date trends, collection coverage and freshness. Unknown ownership is retained. Daily unique opens are not period-unique recipients; incomplete or invalid rates are null with a reason. Account-total fallback measurements are excluded from this report. Read them through query on klaviyo_deliverability_daily with grain=account; they have no provider attribution. Collection coverage explains when fallback occurred. Account selectors use the same tenant-scoped account names/IDs/groups as get_performance.

get_entities

ChatGPT
Structural fetch of platform entities. Args: brand: Brand name or id to query. Required when you can reach more than one brand; optional (auto-selected) when you can reach exactly one. Call list_brands to see options. account: Optional Connected Account or list of them (each a display name, platform account id, Kreel id, or a saved Account Group name, which resolves to its member set; several values union) to narrow the entities to. Omit it to list the entities of every account the brand holds on this platform; a brand with several accounts on one platform then gets an accounts manifest naming them. An account the brand does not hold returns {"error": "unknown_account", "available_accounts": [...]}. platform: "meta" | "google" | "tiktok" | "klaviyo" | "shopify". entity_type: platform-specific (see supported_combinations in errors). Beyond id/platform_id/name/status, entities carry synced structural fields (omitted when unset): - meta campaign: objective, effective_status, daily_budget, lifetime_budget, budget_remaining, buying_type - meta adset: campaign_id, effective_status, daily_budget, lifetime_budget, bid_amount, bid_strategy, optimization_goal, start_time, end_time - meta ad: campaign_id, adset_id, effective_status - google campaign: channel_type, channel_sub_type, serving_status, is_removed, bidding_strategy_type, budget_period (DAILY | CUSTOM_PERIOD), budget_delivery_method, budget_explicitly_shared (budget amounts and pacing: get_budget_pacing) - tiktok campaign: objective_type (raw TikTok objective, e.g. WEB_CONVERSIONS / PRODUCT_SALES / SHOP), campaign_type, operation_status, secondary_status, is_deleted, budget, budget_mode, created_time, modified_time - tiktok adset (= TikTok ad group): campaign_id, operation_status, secondary_status, is_deleted, optimization_goal, optimization_event, promotion_type, budget, budget_mode, schedule_type, schedule_start_time, schedule_end_time, and the attribution windows TikTok locked for the ad group (click_attribution_window, view_attribution_window, engaged_view_attribution_window, attribution_event_count) — absent when TikTok did not report them, never inferred - tiktok ad: campaign_id, adgroup_id, operation_status, secondary_status, is_deleted, ad_format, video_id (fetchable creative asset; absent for Spark Ads without one), tiktok_item_id, identity_type, call_to_action, landing_page_url - klaviyo campaign/flow: tags as `[{id, name}] - klaviyo flow: last_sent_date, the latest day it delivered a message in retained history (null when none is retained). status is the current state only, so a draft flow can still show sends in metrics_7d TikTok status is the effective delivery state (secondary_status, e.g. CAMPAIGN_STATUS_ENABLE / CAMPAIGN_STATUS_DELETE) falling back to the advertiser's operation_status; deleted entities are kept so their history stays readable, filter {"is_deleted": false} to hide them. Budget/bid amounts are in the ad account's currency. Google campaign budgets and bid strategies are NOT synced, so they are not returned. Meta budgets: a campaign missing daily_budget/lifetime_budget while its ad sets carry them means Ad Set Budget Optimization (ABO) - read the budget from the ad sets. A campaign carrying a budget while its ad sets don't means Campaign Budget Optimization (CBO) - read it from the campaign. If neither level carries a budget, treat it as unavailable (e.g. an ended or stale budget object) rather than a sync gap; this is a heuristic, not a guaranteed classification. Google serving_status is raw Google API passthrough: its enum (SERVING/NONE/ENDED/PENDING/SUSPENDED/UNKNOWN) has no REMOVED member, because serving eligibility and campaign lifecycle are different Google dimensions. status/is_removed are the authoritative lifecycle fields. To avoid returning a value that contradicts them, serving_status is omitted entirely whenever is_removed` is true. filters: e.g. {"status": "ACTIVE"}. Applied as equality on the matching column. Keys are validated against that (platform, entity_type)'s real table columns; a key outside that set retu…

get_experiments

ChatGPT
Read a saved experiment or a bounded page; reads never compute verdicts.

get_findings

ChatGPT
One normalized, cited list of what needs attention for a brand. Findings come from Kreel's existing detectors, never new logic: anomaly (channel/campaign spend, revenue, orders, roas, cpa, ctr, cpm - the detect_anomalies alerts), deliverability (Klaviyo campaign bounce/spam rates), stockout_risk and dead_stock (inventory cover), high_return_rate (product/variant refund activity), creative_fatigue (Meta ads: CPC rise with hook or ROAS decline, always warn) and pacing (an ad platform whose projected month-end spend, linear from the trailing 7-day average, deviates from its budget plan line beyond the Brand's tolerance, always warn; evidence carries the advisory daily run-rate change - see get_budget_pacing). kinds in the response lists every kind with its owning detector and severity thresholds; sync_stale, proposal_pending, review_due and verdict_landed are reserved and have no detector yet. Each finding carries fingerprint (stable across reads and days for the same brand, kind, subject, metric, direction and severity - use it to dedup), severity (critical | warn), subject (exactly one channel, campaign, ad, product or variant - never a cross-platform blend), window/baseline_window (the detector's own windows: fatigue always compares seven days with the prior seven, inventory uses trailing 28-day velocity, returns compare with the store in the same window), revenue_kind (store_net or platform_attributed; never add the two), evidence (the detector's payload verbatim), source_ids (each a key in sources) and deep_link (the dashboard page with brand and dates applied). Inventory and returns metadata appear once in metadata. Google conversions stay fractional. magnitude is the finding's size as a multiple of its kind's warn threshold (1 = at warn; null when evidence lacks it). page returns exactly the list a dashboard screen's "What changed" rail shows: performance (every kind), ads (Meta/Google/TikTok anomalies and pacing), creative (fatigue), email (Klaviyo anomalies and deliverability) or store (stockout, dead stock, returns). A page list is ranked by severity, then magnitude, then registry order. Args: window: Recent period, default {"days": 7}; or {"start", "end"} ISO dates. baseline_window: Comparison period for anomaly and deliverability findings. Defaults to the 14 days immediately before `window. kinds: Optional subset of the active kinds. An empty list, an unknown kind or a reserved kind returns a structured error. severity: Optional subset of "critical"/"warn". An empty list or an unknown value returns a structured error. account: Optional Connected Account(s) or Account Group name, as in detect_anomalies; an account the brand does not hold returns {"error": "unknown_account", "available_accounts": [...]}. page: Optional dashboard page (see above). Unknown pages, and kinds outside the page's kinds, return a structured error. platform: Optional subject platform (meta, google, tiktok, klaviyo, shopify). Applied before counts and limit. brand: Brand name or id. Required when more than one brand is reachable. limit: Max findings returned (default 50, capped at 200), most severe first. counts covers every match and truncated` says whether more matched.

get_performance

ChatGPT
Normalized performance query across connected platforms. Dimension vocabulary (ADR-0024): account rows use channel with values meta_ads, google_ads, tiktok_ads, klaviyo_email, klaviyo_sms, shopify_store. Unified ad campaign rows use platform with meta/google/tiktok. The platform argument selects an integration (also shopify/klaviyo/all); it is not a channel value or a Connected Account id. Shopify sales_channels are separate order sources. Args: brand: Brand name or id to query. Required when you can reach more than one brand; optional (auto-selected) when you can reach exactly one. Call list_brands to see options. account: Optional Connected Account, or list of them, to narrow the read to — each a display name, platform account id, Kreel id, or a saved Account Group name (a Brand-level named set of accounts; see account_groups in get_brand_context), which resolves to its member set; several values union into one set. Omit it (the default) to read the blend of every account the brand holds, which is what a brand with one account per platform has always got. A brand with several accounts on one platform gets an accounts manifest naming what was folded in, and any read narrowed with this argument carries the same manifest; when the selection is exactly one group, the manifest names it as group. An empty group reads as zero rows with count: 0 — the group exists but has no readable members. Naming an account the brand does not hold returns {"error": "unknown_account", "available_accounts": [...]} (plus available_groups when the brand has groups) rather than an empty result that looks like "no data". platform: "all" | "meta" | "google" | "tiktok" | "klaviyo" | "shopify" | "ga4". "ga4" reads Google Analytics 4 website behavior for the brand's one selected property and web stream, a measurement source kept apart from Shopify commerce and ad-platform attribution. Its entity types: "account"/"website" (sessions, engaged_sessions, engagement_rate, purchase_sessions, purchase_session_rate, ecommerce_purchases, plus ga4_purchase_revenue in the property currency, never to be added to Shopify or ad revenue), "traffic_source" (session source / medium), "channel_group" (GA4 default channel group, paid and unpaid), "campaign" (GA4 session campaign id/name), "landing_page", "device", and "shopping_activity" (observed view_item / add_to_cart / begin_checkout / purchase counts - not a funnel). GA4 filters: channel_group, source_medium, device_category, landing_page (landing pages), campaign_id / campaign_name (traffic); an incompatible filter returns {"error": "incompatible_filter", "compatible_filters": [...]}. filters may also carry min_sessions (int) and, for landing_page, group_by ("device_category" | "channel_group" | "source_medium") to compare pages within one device or source. purchase_session_rate is GA4 sessionKeyEventRate:purchase recomputed from summed purchase sessions / sessions; null means the property has no purchase key event (see meta.warnings), not zero. Every GA4 result carries meta: status (ok | empty | partial | syncing | no_coverage | needs_selection | not_connected | permission_lost), the property / stream, exact windows, property vs brand time zone, currency, last_synced_at, coverage, quality (thresholding, (other) rows, sampling, provisional days) and warnings - disclose them. Rows are sessions-descending; total_rows/truncated say what limit cut. sales_channels does not apply to GA4. entity_type: "account" | "campaign" | "adset" | "ad" | "flow" | "email_campaign" | "search_term" | "keyword" | "product" | "product_group" | "asset_group" | "country". "adset"/"ad" support Meta Ads, Google Ads and TikTok Ads (platform="google" reads AdSetPerformance/GoogleAdPerformanceDaily; for Google and TikTok, "adset" is an ad-group alias — ad-group discovery is via get_performance(entity_type="adset", platform="google"|"tiktok"), or get_entities(platform="tiktok", entity_type="adset")). For "adset"/"ad", platform must be "meta", "google" or "tiktok"; platform="all" returns Meta ads only (Google and TikTok…

get_plan

ChatGPT
The Brand's monthly budget plan: 12 months x lines, in the Base currency. Lines: spend_meta, spend_google, spend_tiktok, other_marketing, net_revenue (money), new_customers (count), mer (ratio), cm1 (money). Each line's values maps every "YYYY-MM" of the version to an amount or null (null = no plan for that cell, never 0). Returns the active version unless version is given, every version's summary, and diff against the previous version. {"plan": null} when the Brand has no plan. For plan vs actual per month and line, query the plan_vs_actual table.

get_portfolio

ChatGPT
Cross-brand performance summary for every brand you can reach, in one call. Built for the agency workflow — "what changed across my clients this week, who needs attention?" — without an agent making N sequential get_performance calls. There is no brand argument: like list_brands, this operates on your ENTIRE reach (CallerReach.accessible_client_ids), never a single selected brand. Rows are grouped under workspaces. A Supabase user who belongs to more than one workspace gets one group per workspace; an API key reaches exactly one workspace, so it always yields a single group. Each group carries workspace_id, workspace_name, total_brands (every brand you can reach in that workspace, before limit truncates the list), and brands (up to limit, SORTED WITHIN THIS GROUP by sort_by — never across groups). Each brand row: - id, name: pass id as the brand argument to get_performance, compare, or get_brand_context to drill into that brand. - currency: the currency the figures are reported in — the Shopify-reported account currency when available (the same one the revenue module resolves), falling back to the brand's configured base currency. There is no FX conversion — a raw revenue/ad_spend comparison across brands with different currencies is comparing different units. - revenue: refund-correct store-net revenue for date_range (the exact Period Revenue basis as get_performance's Shopify revenue_kind= "store_net" — reconciles digit-for-digit with get_performance for the same brand/window). revenue_kind is always "store_net" here; platform-attributed revenue appears only inside platforms. - orders, aov: the same refund-correct Shopify order count backing revenue, and net revenue / orders — aov is null when orders is 0. - store_reason: present when the brand has no Shopify store in scope (none connected, or one still backfilling this window); revenue, orders, aov and mer are then null, never 0, and sparkline is omitted. Say "no store connected" for those figures. - ad_spend: paid-channel (Meta, Google, TikTok) spend from daily_channel_summary for the window, each channel rounded to cents (email/organic carry no ad spend). - mer: store-net revenue / ad_spend (blended marketing efficiency, the same ratio get_performance's account-level summary reports) - null when ad_spend is 0, never a division error, and null with store_reason when the brand has no Shopify store connected (never 0); null without store_reason means no spend. - new_customer_share: new-customer net revenue / store-net revenue (0-1), Brand-new basis (a customer's first included order anywhere in the brand) - the same figure get_performance's account summary reports; omitted when revenue <= 0 (undefined, never 0). The response's new_customers_basis names the basis. - cm1_pct, contribution_after_ads_pct: profit ladder levels as 0-1 shares of net merchandise (ex tax/shipping, current unit cost), null unless the level is reported (below 80% cost coverage, no sales, or no ad account). Ratios only, so they compare across currencies. - payment_fee_rate: payment fees (actual Shopify Payments fees, else gateway rules) as a positive 0-1 share of net merchandise; null unless the ladder's payment_fees level is reported (80% fee coverage). - repeat_rate_90d: share of the brand's customers who placed a second order within 90 days of their first, over ALL retained history (not date_range) - the Repeat screen's default figure and the DSL repurchase_metrics overall order_n=1 row. Customers whose first order is under 90 days old are excluded (right-censoring); repeat_rate_90d_customers is that denominator (omitted when the brand has no orders). repeat_rate_90d is null when no customer is eligible yet, never a measured 0. Observed, not predicted. - champions_share, at_risk_share: the latest weekly RFM run's Champions and At Risk customers as 0-1 shares of all scored customers (each person once across the brand's stores), whatever date_range. Omitted when RFM has never scored a custo…

get_report_scaffold

ChatGPT
Every cited number a templated report needs, assembled server-side. The report template gallery's data half: each template is also a prompt of the same name. You write the prose; the scaffold carries the figures, so never recompute or estimate one. Publish the result with publish_report built on the kreel://report-kit resource. Templates and their window: - board_monthly_summary: YYYY-MM; vs the previous month and the same month last year. - qbr: YYYY-Qn; vs the previous quarter and the same quarter last year. - client_weekly_report: the week's last day as YYYY-MM-DD; vs the previous 7 days. - promo_recap: START..END (required); vs baseline (START..END, must not overlap) or, by default, the equal-length window just before it. - creative_review: START..END; vs the previous equal window. - portfolio_rollup: YYYY-MM; every Brand you can reach (agency internal, brand is ignored), each vs its equal-length previous period. Omit window (or pass 'latest') for the last complete period. Windows are inclusive days on the Brand reporting calendar and must end before today. The response has window, comparisons (a comparison whose history the Brand's sources do not cover comes back with status 'omitted' and its reason), sections and sources. Each section has status ('ok' or 'omitted' with omitted_reason, e.g. a platform that is not connected: render the reason, never a zero), values (metrics with comparisons deltas and, where the Brand has one, a target chip: the active budget plan's line for each month, else the saved Monthly target, the same target Target pace and plan_vs_actual use), rows (tables), notes, truncated (rows cut by the section's cap) and source_ids. Every number sits under a source_id that resolves in sources, which is also the report's definitions footer. Store revenue is refund-correct net. A section with do_not_add holds platform-attributed figures: never add them across channels or to store revenue. Contribution margin appears only with its cost coverage and the current-cost label. Google conversions are fractional; email rates are 0-1. Args: template: One of the template names above. window: The template's window (see above); omit for the latest. baseline: promo_recap only: START..END, or omit for the default. brand: Brand name or id. Required when more than one brand is reachable; ignored by portfolio_rollup.

get_view

ChatGPT
Read one saved view or Ask snapshot with the numbers its page shows. Returns view (id, name, page, visibility, ephemeral, expires_at and the stored state), the resolved window and comparison dates on the brand's reporting calendar, accounts (the view's account filter and the accounts it selects), data (the page's own payload from the page's builders: Email → email_performance, plus on the Overview tab email_top_flows (its Top flows list), email_klaviyo_status (the account totals), email_time_series and email_deliverability, on the Campaigns or Flows tab email_campaigns / email_flows and email_group_analysis under the page's channel, tag, search, draft and selection filters, on the Flows tab also email_flow_coverage and email_flow_benchmarks (their own last 90 complete days, every Klaviyo account, pinned as of the Ask; medians over the Brands you reach), and on the Audience tab email_audience_growth (over its own pinned weeks), email_lists and email_segments at the pages on screen and selected_audience (the list or segment the growth chart showed, and its view); Creative → creative_summary on the Overview tab, creative_attributes on Attributes (with selected_attribute for an attribute row Ask, and creative_combinations with selected_combination for a Winning combinations row Ask), creative_lifecycle on Lifecycle and creative_gallery on Gallery (its sort, filter bar and "Needs attention" order applied), stage classification pinned to the day asked (view.state.pinned.page_window); Performance → performance; Paid Ads → ads, paid_ads_table and budget_pacing; Website → website_summary and its four breakdowns; Margin → profit_ladder, contribution_target (the monthly target in each month cell, as when asked) and products; Cohorts → cohorts, cohorts_cac, cohorts_repeat and cohort_lens (metric, basis, display, view, outline, added values, best-dimension ranking); Products, Countries, Repeat, Inventory, Segments, a custom dashboard and a Portfolio brand row likewise; long tables return their top 50 rows plus the selected one, and a table the user had searched returns only the rows the search left on screen, with <rows>_searched and its totals whole) and equivalent_args: the get_performance arguments reproducing the view's window and scope, with not_applied naming what does not map. An Ask snapshot (from the dashboard's "Ask about this view") also carries context (the surface, metric, row subject or finding the user asked about) and selected (that row as the page's table shows it). Its dates are pinned: they are the windows on screen when the user asked, however late you read it (pinned: true). A finding's own detector window is in context.finding, separate from the page's dates. Undated pages name their own window in page_window; Repeat's first-order cohort window is pinned too (view.state.pinned.page_window), and Cohorts are read as observed on the day asked (page_window.observed_on: that period stays partial), as is a Portfolio row's payback line (its latest closed cohort stays the one shown after a month midnight). A custom dashboard runs its tiles as they were when the user asked, not as since edited (the revision on screen: a tile saved over before the Ask is refused, never captured); a table tile row Ask adds selected_tile_row, the row found again by its grouped values (context.tile_row.key). An Ask about a fixed aggregate (a Margin ladder line, a Cohort matrix cell, an RFM grid or migration cell, a group metric row, a deliverability provider, a platform card, a lifecycle stage) names it in context.aggregate by the page's own identifiers, and selected.row is that aggregate as the page data shows it. A customer segment Ask reads through the segment service: segment_metrics and segment_history (through context.segment.through_week, with selected_segment_week the row's latest week; empty when context.segment.history_empty, a row asked before its first weekly run) for a saved segment at the version on screen…

get_weekly_growth_brief

ChatGPT
Return a deterministic, source-cited seven-day growth brief. Inventory includes the top three stockout risks, dead stock and coverage counts. Current catalog stock uses velocity ending at the brief end, capped at yesterday; advertising is partial, Meta only and tagger-dependent. Args: brand: Brand name or id to query. Required when the caller can reach more than one Brand. week_ending: Optional YYYY-MM-DD final day. It must be before today. Omit it for the last seven complete reporting-calendar days. Includes experiments due for review and verdicts landed in the window, with source links to the immutable ledger. Reads never trigger review. Includes creative_fatigue: independent Meta alerts, all-ad counts and daily spend, up to ten examples and resolvable source citations. Diagnostic windows end no later than yesterday; missing data is disclosed. The response includes refund-correct store revenue, refunds, paid spend, MER, per-platform ROAS in metrics.platform_roas, each labelled by its source (platform claims overlap and are never summed), CAC based on Shopify first orders, Klaviyo attributed revenue, campaign movers, threshold anomalies, fixed-rule next actions, and a citation for every source. Store and attributed revenue are different bases and must not be added.

list_agent_jobs

ChatGPT
List the Brand's scheduled agents and the templates a job can run. Each job shows its template, schedule (Brand-local), output (a hosted report or a Slack/email message), destinations, approval mode (auto, or hold drafts for an admin), status, next run and last run. Run history is on each run's id: call `get_agent_run`.

list_brands

ChatGPT
List the brands (clients) you can query. Use this first when you can reach more than one brand, then pass the chosen brand's name or id as the brand argument to other tools. If a brand's display name can't be resolved, name falls back to the id string. Each brand carries health, the same sync-health verdict the dashboard's status pill shows: state is the worst of its connected sources (reauth > failed > stale > backfilling > fresh, or none with nothing connected; a disconnected source's retained data is listed but never rolled up), and sources lists each source's status, reason, data_through, last_success_at and backfilling progress. Check it before trusting a brand's recent numbers: reauth and failed mean data stops at that source's data_through.

list_dashboards

ChatGPT
List the custom dashboards you can read on a brand. You see the brand's dashboards, the workspace's templates, and your own private ones. Each entry names its visibility, creator, range, tile count and whether you can edit it. Read one with its tiles' numbers via get_dashboard; build one with create_dashboard. Args: brand: Brand name or id. Required when you can reach more than one brand.

list_views

ChatGPT
List the dashboard saved views you can read on a brand. A saved view is a named page state from the Kreel dashboard: date range, Compare To, account and sales-channel filters, and the page's own filters (Creative: platform and filter bar; Email: tab and filters). You see brand views, the workspace's templates, and your own private views. Each entry names its page, visibility, creator and whether it is the brand default or your default. Read one with get_view, or pass its id as view to get_performance or query. Ask snapshots (from "Ask about this view") are not listed; their id arrives with the question. Args: brand: Brand name or id. Required when you can reach more than one brand. page: "creative" | "email" to narrow the list; omit for every page.

propose_decision

ChatGPT
Queue a fully materialized action for human approval. Nothing executes now. This tool only stores a pending proposal inside Kreel: nothing is published, sent or changed anywhere else. An owner/admin reviews the frozen proposal in the Kreel dashboard Decisions queue; approval executes exactly this payload server-side, unchanged. Proposals are immutable: to change one, propose again with supersedes=<decision id>, which withdraws the old proposal. payload is one of (keyed by action_type): - publish_report {title, html, supersedes?, correction_note?}: the complete report HTML. It is frozen now as a draft (report kit, assets, branding and data-trust footer, exactly as publish_report would store it; the returned decision carries its report_id). Only a human approving it in the dashboard publishes it, unchanged; no public URL serves it before then. A live report with the same title is refused unless supersedes names it; correction_note (one line on what changed) is required with supersedes. - record_learning {topic, content, evidence?} - update_section {key, content_md}: key is positioning, icp, competitive_landscape or brand_voice. rationale (<= 4000 chars) argues why; evidence cites up to 20 source references (e.g. source_ids from the growth brief or get_experiments). verification is optional: the experiment shape from record_experiment (hypothesis, subjects, expectation, baseline, controls) without dates. On approval an experiment opens whose observation window starts the Brand calendar day after execution and lasts review_window_days (1-90, default 14); its verdict is computed on the first read after that. Omit it when the action has no falsifiable metric outcome (a report usually has none); the decision then closes as reviewed_no_verdict. Proposals expire after expires_in_days (1-30, default 14). A brand can hold at most 50 open proposals. Call get_decisions (or read get_brand_context decisions) first so you do not re-propose something already pending or rejected.

publish_report

ChatGPT
Publish an HTML report and get back a shareable URL, or save it as a draft for approval. Drafts and approval. With `draft=true, or whenever the brand requires approval for agent-published reports (on by default for Agency workspaces), nothing is published: the complete report is frozen as a draft and queued as a publish_report decision for an owner/admin to review in the Kreel dashboard (Reports > Drafts). The call then returns status="draft", the report_id and decision_id, url=null and viewable=false; no public URL serves a draft. Approval publishes exactly the frozen draft. Follow up with get_decisions(decision_id=...): a rejected draft carries the reviewer's rejection_reason - address it before proposing again. rationale (why this report, <= 4000 chars) and evidence (up to 20 source references) are shown to the reviewer. **To correct or retry a previous publish, never call this twice with the same title.** If a LIVE report (not yet superseded) with that exact title already exists in this brand, the call is refused - no new report is created - and the refusal names the existing report's existing_slug/existing_url. Get human approval for the replacement, then re-call publish_report with supersedes=<existing_slug>. To publish an unrelated report that happens to share a title, change the title instead. supersedes accepts any report previously published in this brand, including one that has already been superseded itself - it always links at the current end of that report's correction chain, so old shared links keep resolving forward to the newest version. Reports are otherwise frozen: there is no edit call, and deletion is a permanent, human-only action in the dashboard - this tool can only create. The report is hosted at {handle}.reports.kreel.ai/{slug} and is **frozen** (immutable) once published. Access is gated by the Brand's report password, which an owner/admin sets in the dashboard /reports section; until a report handle is claimed AND a password is set, the report is not viewable. Serving also requires the owning Workspace to be in good billing standing - a report goes dark (stops serving) if the workspace subscription is cancelled or unpaid. **Images are snapshotted at publish - short-lived signing does not matter.** Any <img> (or other URL) pointing at a Kreel-hosted creative asset - the Supabase signed URLs of the form …/storage/v1/object/sign/creative-assets/… returned as image_url / video_url by get_creatives(fields="all") - is **copied into this report's own storage at publish and inlined into the frozen document** (oversized assets fall back to a durable /{slug}/asset/… on the report origin), served permanently. So the 1-hour expiry on those signed URLs is irrelevant: it only has to be valid at the moment you call publish_report. You do NOT need to strip photos to keep a frozen report from breaking - reference the signed URL as-is. What is NOT snapshotted, and WILL 404 / rot in the frozen report: relative or local paths (e.g. assets/foo.jpg - they resolve against your local machine, not the report origin) and non-Kreel URLs (e.g. Meta CDN links, which expire on Meta's schedule). Note that get_creatives(fields="all") is not a guarantee of the safe form: image_url / video_url return the Kreel signed URL only when the asset has been stored, and otherwise fall back to a Meta CDN URL that will rot. So reference a creative URL as-is only after confirming it is the signed form (…/storage/v1/object/sign/creative-assets/…); if it is a Meta CDN link the asset isn't yet freezable - re-sync/backfill the creative, or omit it. Kreel automatically adds a frozen data-trust section to the stored HTML. It identifies connected sources, their last successful sync and data-through dates, the definitions of common report metrics, and the attribution caveat that platform-reported revenue overlaps and must not be added across channels. This section is derived server-side and does not need to be included in `h…

query

ChatGPT
Run a JSON Query DSL expression against the normalized data model. inventory_cover exposes current canonical catalog stock, velocity and days_of_cover. No date filter or group_by; select/where/order_by/limit/cursor are supported. Metadata states the 28-day completed window and partial Meta tagger coverage. cost_coverage is one undated row per brand: catalog and trailing-90-day revenue cost coverage, uncovered variants and revenue_at_risk_90d. Load costs with set_product_costs. product_margin, variant_margin and margin_summary price retained history at current unit cost, excluding tax/shipping. Require bounded where.date, no group_by. Every row reports cost coverage; margin is null below 80% revenue coverage. margin_summary includes blended fulfillment and margin MER (not attributed ROAS). profit_ladder returns the margin levels (net_merchandise → cm1 → payment_fees → ad_spend → contribution_after_ads) per period: bounded where.date, where.grain eq total|day|week|month, no group_by. payment_fees_by_gateway returns fees per gateway by source (actual | rule | missing) with coverage: bounded where.date, no group_by. plan_vs_actual returns the budget plan (get_plan/set_plan) next to monthly actuals per line; where.version eq N picks a version (default active), where.date is optional, no group_by. product_returns and variant_returns expose bounded refund activity: require where.date with eq or gte/lte ISO dates. Select sold_units, refunded_units, sold_revenue, refunded_revenue, return_rate, revenue_return_rate, store_return_rate, baseline_delta (fraction), baseline_multiple and advertising. Identity filters, projection, ordering and pagination apply after the scoped weighted baseline; group_by is unsupported. Variant IDs are account/product local ("unknown" for absent IDs); canonical parent fields identify rollups. meta includes window, account coverage, currency, basis and unassigned totals. email_flow_types is one row per Klaviyo flow: flow_type (welcome, abandoned_checkout, ...), the source rule and a reason, overridden and live. email_flow_benchmarks is one row per (flow, metric) over the last 90 complete days: value, own_baseline (the 90 days before) and workspace_median of the flow type across this Workspace's Brands you reach, null below 5 (workspace_n). Rates are 0-1 fractions; no group_by or account. ga4_site, ga4_traffic, ga4_landing_pages and ga4_events are GA4 website rows for the brand's one selected property/stream (session-scoped acquisition; dates are the property's calendar). engagement_rate and purchase_session_rate recompute from summed numerators/denominators under group_by; a null purchase_session_rate means no purchase key event, not 0. purchase_revenue is unconverted, in the property currency, and never additive with Shopify or ad revenue. ga4_events counts view_item/add_to_cart/begin_checkout/purchase independently (not a funnel). For coverage, thresholding and warnings use get_performance(platform="ga4"). The DSL is the escape hatch for questions the canonical tools don't cover. Shape: { "from": "campaigns", "where": {"platform": {"eq": "meta"}, "spend": {"gt": 100}}, "select": ["platform", "spend", "roas"], "group_by": ["platform"], "order_by": [{"field": "spend", "dir": "desc"}], "limit": 50, "account": ["EU Store", "act_123"] } See kreel://schema for the full table/column/operator reference. Semantics to be aware of: - daily_channel_summary uses channel: meta_ads, google_ads, klaviyo_email, klaviyo_sms, shopify_store. campaigns and adset_performance use the ad-only platform: meta, google (ADR-0024). For example, {"from": "daily_channel_summary", "where": {"channel": {"eq": "meta_ads"}}, "select": ["channel", "spend"], "group_by": ["channel"]}. MCP integration selectors also called platform accept broader values; they do not add columns to provider-specific DSL tables. Neither dimension identifies a Connected Account or a Shopify sales channel. - No default time window - results span all synced history unless you add a where on the table's date column. - Top-level account (optiona…

record_experiment

ChatGPT
Freeze a hypothesis, homogeneous typed subjects, metric expectation and inclusive dates. Review becomes eligible the following Brand-calendar day. Selectors support future naming matches. No holdout — observational comparison. See docs/test-ledger.md.

record_learning

ChatGPT
Persist a durable, reusable brand insight. With id, this UPDATES that existing learning (the merge path) — pass clear=["evidence"] to unset its evidence. Without id it creates a new one and returns related — the brand's existing active learnings on the same topic. If related is non-empty, prefer calling this again with that learning's id to consolidate instead of duplicating. related matches topic case-, spacing-, and underscore-insensitively (underscores are treated as separators) among active learnings, so re-cased, differently- spaced, or snake_case duplicates of the same topic still surface. Args: brand: Brand name or id. Required when you can reach more than one brand.

remove_competitor

ChatGPT
Delete a competitor by name. This permanently removes the saved row. name is matched case-, spacing-, and underscore-insensitively (underscores are treated as separators), so "GLOSSIER" or "mcp_audit_test" removes "Glossier"/"MCP Audit Test". If nothing matches, the current competitor names are returned so you can retry with one that exists. Args: brand: Brand name or id. Required when you can reach more than one brand.

remove_partner

ChatGPT
Delete by case-insensitive name. Its codes become unmapped on the next read.

review_experiment

ChatGPT
Compute the first verdict and linked learning atomically after the review date. Annotation cannot override computed numbers. Subsequent calls return the saved result.

save_customer_segment

ChatGPT
Save a Brand-wide customer segment and return its definition and aggregates. A rule is an AND/OR tree: {"and": [...]}, {"or": [...]} or a condition {"feature", "operator", "value"}; at most 8 levels, 100 conditions and 100 values per list. Features: rfm_segment, rfm_recency_score, rfm_frequency_score, rfm_monetary_score, orders_count, net_ltv, aov (money: Base currency), days_since_last_order, overdue_status, first_product, product_ever_bought (values '<account_id>:<product_id>' from the catalog), acquisition_channel, country (ISO alpha-2, ZZ unknown), discount_code_share (0-1), accepts_marketing, shopify_tags. Numeric: eq ne gt gte lt lte in nin; categorical: eq ne in nin; boolean: eq ne; scalars also is_known / is_unknown (no value); product and tag sets: contains_any contains_all contains_none. An unknown value never matches, including ne and nin. Example "Lapsed VIPs": {"and": [{"feature": "rfm_monetary_score", "operator": "gte", "value": 4}, {"feature": "overdue_status", "operator": "eq", "value": "lapsed"}]}. Each person counts once across the Brand's stores (email hash, else store-local). Aggregates are size, 365-day refund-net revenue of orders dated in the window (Base currency; not Period Revenue), observed repeat share and RFM overlap. No members are returned and nothing is sent to any platform. Needs the write scope and the member role. Args: name: Unique per Brand, ignoring case (at most 120 characters). rule: The rule tree. brand: Brand name or id. Required when you can reach more than one brand. segment_id: Update this segment (saves the next version) instead of creating one. expected_version: Required with segment_id: the version you are editing.

schedule_agent_job

ChatGPT
Schedule a deterministic Kreel template to run on a Brand-local cadence. Needs the admin role on the Brand and a write-scoped credential owned by a user (a legacy key with no owner is refused, `owner_required). You become the job's owner: every run re-checks that you still hold admin on the Brand and that the workspace is subscribed, and skips otherwise. Templates (list_agent_jobs lists them with their params): - weekly_growth_brief: the Weekly Growth Brief for the last seven complete days. - findings: new findings for yesterday; params kinds, min_severity. With output="message" it is an alert: nothing new sends nothing. - pacing: month-to-date pace against the Brand's targets. Args: name: Label shown in the dashboard. template: One of the template keys above. There is no LLM runtime: runtime other than deterministic is refused (ADR-0034). schedule: {frequency: daily|weekly|monthly, hour: 0-23, weekday: 0-6 (weekly, 0 = Monday), day_of_month: 1-28 (monthly)} on the Brand reporting calendar. Missed hours are not backfilled. output: report publishes a hosted Report each run; message sends the brief to Slack and/or email only. destinations: {slack: bool, email_recipient_ids: [...]}. Slack uses the Brand's alert webhook; recipients come from the Brand's digest list. A message job needs at least one. approval_mode: auto delivers each run; hold` keeps each run as a draft until an owner/admin approves it in the dashboard. brand: Brand name or id. Required when you can reach more than one.

set_competitor

ChatGPT
Add or update a competitor by name. Partial — omitted fields are left unchanged on an existing competitor. To unset a field, name it in clear (e.g. clear=["handle"]). name is matched case-, spacing-, and underscore-insensitively (underscores are treated as separators), so "glossier" or "mcp_audit_test" updates an existing "Glossier"/"MCP Audit Test" (and refreshes its stored casing to the new input) rather than creating a duplicate. Args: brand: Brand name or id. Required when you can reach more than one brand.

set_creative_tag_schema

ChatGPT
Read or replace the Brand's custom creative tag dimensions in Kreel. With no dimensions and no copy_from_brand it returns the current schema (any role). Writing needs the owner or admin role. dimensions replaces the schema wholesale: a list of up to 5 objects {"key", "label", "description"?, "values"} where key is a lowercase slug (not a built-in tag or naming field) and values are 2-20 allowed strings. Pass [] to clear it. copy_from_brand copies another Brand's schema (same Workspace only); copying a Brand without one clears this one. Saving prunes stored values the new schema no longer allows (hand-set ones included) and makes every creative a re-tag candidate: Kreel's own AI tagger fills the new dimensions within the daily re-tag cap. Nothing changes in the ad accounts or anywhere outside Kreel. Values are then readable per creative (get_creatives custom_tags), groupable as custom:<key>, and queryable in the creative_custom_tags DSL table. The fixed AI dimensions and their vocabularies come back in fixed_dimensions.

set_partner

ChatGPT
Create or update by case-insensitive name; omitted fields are preserved. Codes replace the saved list; pass [] to clear. All retained orders attribute immediately. Types: influencer, affiliate, podcast, other.

set_plan

ChatGPT
Save the Brand's whole monthly budget plan as a new version. start_month is "YYYY-MM"; the plan covers it and the next 11 months. lines maps a line id to {"YYYY-MM": value or null}; omitted cells and nulls are blank. Replaces the whole grid: send every line you want kept (read get_plan first). Rules: spend and other_marketing and net_revenue >= 0; cm1 any number; new_customers a whole number >= 0; mer > 0; amounts in the Brand's Base currency. Any invalid cell writes nothing. A grid identical to the active version saves nothing (changed: false); otherwise the previous version stays readable. Owners and admins only (write scope). Audit-logged.

set_product_costs

ChatGPT
Set current unit costs (COGS) for catalog variants, e.g. from a supplier price list. Each item: unit_cost (required; store currency, positive, at most 4 decimals; an explicit null or "" clears the manual override), currency (must equal the store currency; never converted), and one reference: variant_id, else sku (normalized), or product_id alone for a product-level cost. account_id disambiguates a SKU sold by several stores. Costs merge into the saved overrides. Returns per-row status (add/change/clear/unchanged/error) and the projected catalog coverage. Any row error writes nothing; call with dry_run=True first. Costs are current, not cost at sale: they re-state every margin window. Owners and admins only (write scope). Audit-logged.

update_brand_profile

ChatGPT
Update the Brand Profile typed anchors. Partial — only the fields you pass change. price_tier must be one of value/mid/premium/luxury; margin_band one of lean/moderate/healthy (invalid values are rejected). To unset a field, name it in clear (e.g. clear=["target_roas"]). Monthly targets (one recurring set per brand, paced month to date by get_brand_context / get_portfolio / get_weekly_growth_brief pace): monthly_revenue_target (store revenue, brand base currency), monthly_spend_budget (Meta + Google + TikTok spend), target_mer (store revenue / paid spend), monthly_new_customers_target (brand-new customers) and target_new_customer_cac (paid spend / new customers). Each must be greater than 0. monthly_contribution_target is the monthly contribution after ads (the profit ladder's CM1 minus Meta + Google + TikTok spend, current unit cost, brand base currency). It is not paced; the Margin page shows it in each month's contribution cell, prorated for partial months. It may be zero or negative (a break-even or investment plan). lapse_threshold_days (1-1095, default 180 when unset) is the customer-mix gap: a returning order placed at least this many days after the customer's previous order counts as reactivated in get_performance account summaries, the customer_mix_daily query table and the weekly brief. pacing_tolerance_pct (0-100, default 10 when unset) is the budget pacing tolerance: get_budget_pacing flags a platform off pace, and get_findings raises a pacing finding, when its projected month-end spend deviates from its budget plan line by strictly more than this percentage. Args: brand: Brand name or id. Required when you can reach more than one brand.

update_dashboard

ChatGPT
Change a Kreel dashboard; omitted arguments keep their value. Only Kreel members see dashboards; nothing leaves the Workspace. tiles, when given, replaces the whole tile list (same tile shape and rules as create_dashboard; a mixed-basis tile rejects the update and nothing changes). You can edit your own dashboards; an admin can edit any shared one. Only the creator can make a shared dashboard private. Args: dashboard_id: The dashboard id (from list_dashboards). brand: Brand name or id. Required when you can reach more than one brand. name: New name. description: New description. visibility: "private" | "brand" | "workspace". range: New saved range, {preset, days, start_date?, end_date?}. tiles: The complete new tile list.

update_section

ChatGPT
Create or replace a prose Knowledge Section (markdown). key must be one of: positioning, icp, competitive_landscape, brand_voice. One row per key — this replaces the section's content. Args: brand: Brand name or id. Required when you can reach more than one brand.

withdraw_decision

ChatGPT
Retract one of this brand's proposed decisions before a human acts on it.

Capabilities

Writes

Example Prompts

Click any prompt to copy it.

App Stats

45

Tools

3

Prompts

ChatGPT

Platforms

Works with

ChatGPT

Data refreshed daily