Skip to content

Brand context tools

The Brand Knowledge Base is a per-brand store of durable context: a typed profile, prose sections on positioning and voice, a list of named competitors, and learnings that accumulate over time. An agent reads it before analysis so its recommendations are anchored in what your brand actually cares about, and writes to it directly with the tools on this page.

You can also view and edit the Knowledge Base by hand in the dashboard. See Brand Knowledge Base.

The preflight call. Call this before asking an agent to analyze data or write a report, so it knows whether the data is current and trustworthy, and what the brand’s targets and voice are.

get_brand_context(brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | brand | string | Brand name or id. Required only when the caller can reach more than one brand; auto-selected otherwise. |

The response bundles the Knowledge Base together with operational fields (connection status, a data readiness check, data coverage, and the brand’s saved Account Groups) used for that same preflight - see Data tools for those. Its sources block is the per-source freshness the dashboard shows in every report header, so an agent quotes the same freshness a person sees. One entry per connected source (plus a disconnected source whose retained data still feeds reports):

| Field | Description | |---|---| | platform, label | The source, e.g. shopify / Shopify. | | data_through | The latest day the source’s queryable data covers. GA4 reads its report coverage. | | last_success_at | When the latest fully successful performance sync finished (UTC). With several accounts on a source, the account the status comes from (the oldest success among equals). | | sla_hours | How long after that success the source still counts as fresh. | | status | fresh, stale, failed or reauth. Each connected account is judged separately and the worst wins: reauth > failed > stale > fresh. | | reason | Why it is not fresh: past_sla, never_synced, sync_failed, sync_partial, reauth_required, token_expired, needs_selection, token_error or disconnected. null when fresh. |

The Knowledge Base portion of the response:

| Field | Description | |---|---| | context | The typed profile: vertical, price_tier, margin_band, target_roas, target_cpa, target_aov. Any field can be null. | | sections | Array of {key, content_md, updated_at} - the four prose sections described below. | | competitors | Array of {name, handle, differentiator}. | | learnings | Array of {id, topic, content, evidence}. Active learnings only. | | learning_cap | {active, cap} - how many active learnings exist against the soft cap. | | experiments | Due-for-review and recently-decided experiments - the same summary get_weekly_growth_brief cites. | | is_empty | true only when the profile, sections, competitors, and learnings are all unset. |

Six tools cover the Knowledge Base’s four parts. Each takes an optional brand argument with the same rule as get_brand_context, and each requires your OAuth login or API key to carry write scope (see API keys). Because they change stored data, most MCP clients prompt you to approve each call before it runs; remove_competitor is marked as a stronger, destructive action since it deletes a row.

Partial update to the typed profile. Only the fields you pass are changed.

update_brand_profile(
vertical: str | None = None,
price_tier: str | None = None,
margin_band: str | None = None,
target_roas: float | None = None,
target_cpa: float | None = None,
target_aov: float | None = None,
clear: list[str] | None = None,
brand: str | None = None,
) -> dict

| Parameter | Type | Description | |---|---|---| | vertical | string | Free-text category, for example “skincare”. | | price_tier | string | One of value, mid, premium, luxury. | | margin_band | string | One of lean, moderate, healthy. | | target_roas | number | Target return on ad spend. | | target_cpa | number | Target cost per acquisition. | | target_aov | number | Target average order value. | | clear | list of strings | Profile field names to unset. | | brand | string | Brand name or id. |

An invalid price_tier or margin_band value is rejected rather than saved.

Creates or replaces one prose section. This is a full replace, not a merge with the existing content.

update_section(key: str, content_md: str, brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | key | string | One of positioning, icp, competitive_landscape, brand_voice. | | content_md | string | The section’s content, as markdown. | | brand | string | Brand name or id. |

There is one row per key per brand. Writing an unrecognized key is rejected.

Adds a competitor, or updates one if the name already matches. Matching on name is case-, spacing-, and underscore-insensitive (underscores count as separators too), so writing “glossier” or “mcp_audit_test” updates an existing “Glossier” or “MCP Audit Test” row instead of creating a duplicate, and the stored name’s casing is refreshed to whatever was just written.

set_competitor(
name: str,
handle: str | None = None,
differentiator: str | None = None,
clear: list[str] | None = None,
brand: str | None = None,
) -> dict

| Parameter | Type | Description | |---|---|---| | name | string | The competitor’s name. Used to match an existing row. | | handle | string | Handle or URL. | | differentiator | string | A one-line note on how they differ from this brand. | | clear | list of strings | handle and/or differentiator, to unset them. | | brand | string | Brand name or id. |

On an existing competitor, any field you don’t pass is left unchanged.

Permanently deletes a competitor by name, using the same case-, spacing-, and underscore-insensitive match as set_competitor.

remove_competitor(name: str, brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | name | string | The competitor’s name to remove. | | brand | string | Brand name or id. |

If no competitor matches, this is not an error: it returns the current list of competitor names so the agent can retry with the right one.

Creates a new learning, or updates an existing one in place when you pass its id.

record_learning(
topic: str,
content: str,
evidence: str | None = None,
id: str | None = None,
clear: list[str] | None = None,
brand: str | None = None,
) -> dict

| Parameter | Type | Description | |---|---|---| | topic | string | What the learning is about. | | content | string | The insight itself. | | evidence | string | What supports it, optional. | | id | string | Omit to create a new learning. Pass an existing learning’s id to update it instead. | | clear | list of strings | Only "evidence" can be cleared this way. | | brand | string | Brand name or id. |

Creating a new learning (no id passed) returns related: the brand’s existing active learnings on the same topic, matched case-, spacing-, and underscore-insensitively. A non-empty related list is a signal to merge into one of those instead of keeping a near-duplicate, by calling record_learning again with that learning’s id. Updating by id always returns an empty related list.

Active learnings are capped at 50 per brand as a soft signal, not a hard limit: writes are never blocked at the cap, but it’s a cue to archive stale ones. Check learning_cap from get_brand_context to see where a brand stands.

Soft-archives a learning, removing it from the active set and the learning_cap count.

archive_learning(id: str, brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | id | string | The learning’s id. | | brand | string | Brand name or id. |

There is no unarchive tool. Archiving is a one-way move from an agent’s perspective; restoring a learning means recording it again.

Two tools maintain the brand’s partner/influencer registry: named partners (creators, affiliates, podcasts) mapped to the discount codes their audience uses. Once a partner’s codes are registered, every retained order using one of them attributes to that partner immediately - see the partner_attribution and partner_unmapped_codes tables on the Query DSL reference for reading the attributed revenue back out. Both tools require write scope and are shown to you for approval before running, the same as the Knowledge Base write tools above.

Creates a partner, or updates one if the name already matches, ignoring case only (unlike set_competitor, spaces and underscores must match exactly).

set_partner(
name: str,
partner_type: str | None = None,
codes: list[str] | None = None,
notes: str | None = None,
brand: str | None = None,
) -> dict

| Parameter | Type | Description | |---|---|---| | name | string | The partner’s name. Used to match an existing row. | | partner_type | string | One of influencer, affiliate, podcast, other. Defaults to other on create. | | codes | list of strings | The partner’s discount codes. Replaces the saved list outright - pass [] to clear all codes, or omit to leave the saved list unchanged. | | notes | string | Free-text notes. | | brand | string | Brand name or id. |

Any field you don’t pass on an existing partner is left unchanged - except codes, which is a full replace whenever you do pass it.

Permanently deletes a partner by name, matched case-insensitively like set_partner.

remove_partner(name: str, brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | name | string | The partner’s name to remove. | | brand | string | Brand name or id. |

Its codes become unmapped on the next read - they show up under partner_unmapped_codes instead of disappearing silently.

The budget plan is the brand’s monthly plan for 12 consecutive months: ad spend per platform, other marketing, net revenue, new customers, MER and CM1. Money is in the brand’s Base currency. Every save creates a new version, and earlier versions stay readable. The dashboard’s Plan page edits the same plan, and the plan_vs_actual query table puts each month’s actuals next to it.

Returns the active plan version, or version when given.

get_plan(version: int | None = None, brand: str | None = None) -> dict

The response is {"plan": {...}}, or {"plan": null} when the brand has no plan. The plan carries version, start_month, months (the 12 YYYY-MM keys), currency, and lines. Each line has line, label, unit (money, count or ratio) and values, which maps every month to a number or null. A null is a blank cell meaning no plan, never 0. It also lists every version in versions and the changed cells since the previous version in diff. The response has the same shape as GET /api/budget-plan.

Saves the whole grid as a new version. It requires write scope and the member role or above on the brand; viewers are refused.

set_plan(start_month: str, lines: dict, note: str | None = None, brand: str | None = None) -> dict

| Parameter | Type | Description | |---|---|---| | start_month | string | YYYY-MM. The plan covers this month and the next 11. | | lines | object | Line id to {"YYYY-MM": number or null}. A line or month you leave out is blank. | | note | string | Optional note shown in the version history, up to 500 characters. | | brand | string | Brand name or id. |

The line ids are spend_meta, spend_google, spend_tiktok, other_marketing, net_revenue, new_customers, mer and cm1. Spend, other marketing and net revenue must be at least 0. CM1 can be any number. New customers must be a whole number of at least 0, and MER must be greater than 0.

set_plan replaces the whole grid, so read get_plan first and send back every value you want to keep. One invalid cell rejects the whole save, and the error lists every problem. A grid identical to the active version saves nothing and returns changed: false. A brand without a Base currency cannot save a plan.

Email support@kreel.ai.