Docs
IntegrationsHeading MCP

Tools reference

Every tool the Heading MCP gives a client, with its arguments, what it returns and an example call

The Heading MCP gives every connection the same 52 tools, whichever way it connects. Every tool except list_brands takes a required brandId, from list_brands. Your client may show each tool by its label or by its name; each section below is headed with both.

A read changes nothing, and your client can run it without asking you. A write or a delete is marked so in its listing, and most clients ask you before they run one. A write needs a credential that can write: see Writes and limits.

Lists return up to 1,000 rows a page, 100 when you name no limit, with total and a nextCursor to pass back as cursor. When a call is refused, the answer says why and how to fix it; see Errors.

Brands

Where every conversation starts: the brands this connection can reach, and their settings.

Brands (list_brands)

list_brands. Read. Changes nothing.

The brands this connection can work on, each with its id, name and domains, its profile (summary, positioning, description), the AI platforms it is tracked on, its collection cadence, its primary, measured and target markets, and its prompt quota (used and total). Every other brand tool takes a brandId, and this is where the ids come from: call it first in any conversation that has not already established which brand is in scope. A brand missing from this list cannot be reached through any tool.

Arguments

ArgumentTypeRequiredWhat it is
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with brands, nextCursor, total.

Example call

{
  "limit": 100
}

Change brand settings (update_brand)

update_brand. Write. Most clients ask you before they run it. Any team member with access to the brand, for some fields; the rest are for team owners and admins.

Changes the brand's settings, exactly as brand settings and the brand profile in the dashboard change them. Send only the fields to change; at least one is needed. name renames the brand, and because Heading finds the brand in answers by its name, every stored answer is checked again under the new name. description is the brand's own one-line description; null clears it. profile.summary (what the business is and who it serves) and profile.positioning (how it wants AI assistants to describe it) replace those profile sections, recorded as the team's own edit. targetMarkets is the COMPLETE list of markets the business says it sells in and replaces the stored list (an empty list clears it): it is a declaration, so it adds no prompt and starts no tracking; a target market with no prompts shows as a coverage gap. trackedPlatforms is the complete set of AI platforms to ask the brand's prompts on, and changes what every visibility figure is measured over from the next collection day; each brand tracks up to its plan's allowance (3 of the 6 unless more were bought), and a set over it is refused with quota_exceeded, naming the allowance, so nothing is changed. Platform changes are recorded in the measurement changelog that list_changes reads. cadence is daily, or weekly on one fixed UTC weekday. Any member with access to the brand can change the profile sections; only team owners and admins can change the name, description, target markets, tracked platforms or cadence, and a member's call that names any of those is refused whole, naming them, so nothing is changed. The change is made in two steps: the settings first (all of them or none), then the profile sections. If the profile save fails after the settings were saved, the answer says so: changed lists only what was saved and notSaved names the profile sections. Call list_brands first to read the current settings; the answer is the brand after the change and which fields moved. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
cadenceone of daily, weeklyNoHow often Heading collects answers: daily, or weekly on one fixed UTC weekday.
descriptionstring or nullNoThe brand's own one-line description. null clears it.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
namestringNoThe brand's new name.
profileobjectNoProfile sections to replace. Each one sent replaces that section whole.
profile.positioningstringNoHow the brand wants AI assistants to describe it.
profile.summarystringNoWhat the business is and who it serves.
targetMarketslist of objectNoThe COMPLETE list of markets the business says it sells in. It replaces the stored list; an empty list clears it.
targetMarkets[].areaslist of stringNoPlaces INSIDE that market the business named, such as Berlin. Omit when the whole country is the target. Never put a country here.
targetMarkets[].marketstringYesA market tag: a language, a hyphen and a country code, such as en-GB or de-DE. Use the country's main language unless the team says otherwise.
trackedPlatformslist of one of chatgpt, perplexity, ai_overview, ai_mode, gemini, copilotNoThe complete set of AI platforms to track, by id as list_brands reports them. It replaces the current set.

Returns an object with brand, changed, notSaved, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "settings-2026-11-05",
  "name": "Example Ltd",
  "profile": {
    "positioning": "The accounting CRM built for five person firms."
  },
  "trackedPlatforms": [
    "chatgpt",
    "perplexity",
    "gemini"
  ]
}

Prompts

The tracked prompts: the questions Heading asks the AI platforms on every collection day.

Tracked prompts (list_prompts)

list_prompts. Read. Changes nothing.

The brand's tracked prompts: the questions Heading asks the AI platforms every collection day. Each row carries the prompt's id, its exact text, its market, its query type (branded or unbranded), its status, its tags, its target page and its figures across every tracked platform on its latest measured UTC day (Mentioned %, mentions, citations, average rank among tracked brands, sentiment, and the day itself), which are the figures the Prompts page shows. An unmeasured prompt states null, never zero. The answer also states how many prompts the brand holds in each status and its prompt quota (used, total and free slots), whatever the filters. Visibility is measured over the active prompts, so this list defines what every figure means. Filter by status, tags, market, query type or text; page with limit and cursor. Use it before adding prompts, to see what is tracked and how many slots are free, and to find the prompt ids other tools take.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
marketstringNoOnly prompts asked in this market, as a market tag such as en-GB. Omit for every market.
queryTypeone of all, branded, unbrandedNobranded (prompts that name the brand), unbranded (prompts that do not) or all. Defaults to all.
searchstringNoOnly prompts whose text contains this, ignoring case.
statusone of active, inactive, suggestedNoOnly prompts in this status: active (tracked), inactive (paused) or suggested. Omit for every status.
tagIdslist of stringNoOnly prompts carrying at least one of these tags, by id. A tag id the brand does not hold answers not_found.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with counts, prompts, quota, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "limit": 100,
  "market": "en-GB",
  "queryType": "unbranded",
  "search": "pricing",
  "status": "active",
  "tagIds": [
    "tag_1"
  ]
}

Add tracked prompts (create_prompts)

create_prompts. Write. Most clients ask you before they run it. Any team member with access to the brand.

Adds up to 200 prompts to the brand's tracked portfolio in one call. Each prompt starts active: Heading asks it on every tracked platform from the brand's next collection day (today, when today is one), and it counts in every visibility figure from then on, so this changes what the figures measure. Give each prompt's exact text as a person would ask it, and optionally its market (defaults to the brand's primary market), its query type (otherwise decided from whether the text names the brand), tag ids from list_prompts or the brand's tag list, and a target page on the brand's own site. The batch is all or nothing against the prompt quota: if the new prompts do not all fit, nothing is added and the answer is quota_exceeded, stating how many were asked and how many slots are free, so you choose what to drop. A prompt the brand already tracks in that market is reported under alreadyTracked and takes no slot; a prompt the brand removed earlier comes back with its history. Every addition is recorded in the prompt changelog under the person the call acts as. Send an idempotencyKey to make a retry safe: the same key with the same arguments writes once and returns the first answer. Call list_prompts first to see the quota and what is tracked.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
promptslist of objectYesThe prompts to add, in any order.
prompts[].marketstringNoThe market to ask it in, as a market tag such as en-GB. Defaults to the brand's primary market.
prompts[].queryTypeone of branded, unbrandedNobranded when the prompt names the brand, otherwise unbranded. Omit to decide from the text.
prompts[].tagIdslist of stringNoTags to put on the prompt, by id. A tag id the brand does not hold refuses the whole call.
prompts[].targetPagestringNoThe page on the brand's own site the team wants cited for this prompt.
prompts[].textstringYesThe prompt, exactly as a person would ask it.

Returns an object with alreadyTracked, created, quota, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "import-2026-11-05-1",
  "prompts": [
    {
      "market": "en-GB",
      "queryType": "unbranded",
      "tagIds": [
        "tag_1"
      ],
      "targetPage": "https://example.com/pricing",
      "text": "which crm is cheapest for a five person team"
    },
    {
      "market": "de-DE",
      "text": "bestes crm fuer kleine kanzleien"
    }
  ]
}

Change tracked prompts (update_prompts)

update_prompts. Write. Most clients ask you before they run it. Any team member with access to the brand.

Changes up to 200 of the brand's prompts in one call, by the ids list_prompts returns. For each prompt you may pause it or resume it (status paused or active; active also starts tracking a suggested prompt), replace its tags (tagIds) or add and remove some (addTagIds, removeTagIds), set its query type (branded or unbranded), and set or clear its target page (a page on the brand's own site, or null). Changing the text or the market does not move the prompt: a market is part of a prompt's identity, so Heading re-creates it as a new prompt with the new text or market, carries its tags, query type and target page across, and removes the old one, whose past answers stay in every figure; the answer gives the new id. Pausing takes a prompt out of the measured set from that day and resuming puts it back, so status and text edits change what the figures measure. Each prompt answers on its own: updated, unchanged (it already held those values) or failed with its own reason, and a failed prompt writes nothing. The quota rule: every prompt this call would start counting (a paused or suggested prompt set to active) needs a free slot. Pauses in the same call run first and free their slots; if the rest still do not fit, nothing in the call is written and the answer is quota_exceeded with the counts, so you choose what to leave out. Every status change, re-creation and removal is recorded in the prompt changelog under the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
promptslist of objectYesThe prompts to change, each with only the fields to change. Each prompt id appears once.
prompts[].addTagIdslist of stringNoTags to add to the prompt, by id; tags it already carries are kept. Not with tagIds.
prompts[].marketstringNoA new market, as a market tag such as en-GB. Re-creates the prompt in that market; the old prompt is removed with its history kept.
prompts[].promptIdstringYesThe prompt to change, by the id list_prompts returns.
prompts[].queryTypeone of branded, unbrandedNobranded when the prompt names the brand, otherwise unbranded. Recent days' branded and unbranded figures are recalculated.
prompts[].removeTagIdslist of stringNoTags to take off the prompt, by id. Not with tagIds.
prompts[].statusone of active, pausedNoactive tracks the prompt (a paused one resumes, a suggested one starts); paused stops tracking it. Paused is the status list_prompts shows as inactive.
prompts[].tagIdslist of stringNoThe prompt's whole tag set, by id, replacing the tags it carries; an empty list clears them. Not with addTagIds or removeTagIds.
prompts[].targetPagestring or nullNoThe page on the brand's own site the team wants cited for this prompt; null clears it.
prompts[].textstringNoNew wording, exactly as a person would ask it. Re-creates the prompt with this text; the old prompt is removed with its history kept.

Returns an object with quota, replayed, results.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "cleanup-2026-11-05",
  "prompts": [
    {
      "addTagIds": [
        "tag_1"
      ],
      "promptId": "prompt_1",
      "queryType": "unbranded",
      "status": "active",
      "targetPage": "https://example.com/pricing"
    },
    {
      "market": "de-DE",
      "promptId": "prompt_2",
      "text": "bestes crm"
    },
    {
      "promptId": "prompt_3",
      "tagIds": [],
      "targetPage": null
    }
  ]
}

Remove tracked prompts (delete_prompts)

delete_prompts. Delete. Most clients ask you before they run it. Any team member with access to the brand.

Removes up to 200 prompts from the brand's portfolio, by the ids list_prompts returns, as the Prompts page's remove does. A prompt that was ever measured is soft-deleted: it leaves the list, the quota and every figure from today, and its past answers stay in every past figure, so nothing already measured is rewritten. A suggested prompt that was never measured has no history and is deleted outright. An active prompt may be removed; it stops counting from today. Each prompt answers on its own: soft_deleted, deleted, not_found (no live prompt with that id on this brand, including one removed before), or duplicate (the id was already named earlier in the call). A prompt removed by mistake comes back with its history when create_prompts adds the same text in the same market. Every removal is recorded in the prompt changelog under the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
promptIdslist of stringYesThe prompts to remove, by the ids list_prompts returns.

Returns an object with quota, replayed, results.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "cleanup-2026-11-05",
  "promptIds": [
    "prompt_1",
    "prompt_2"
  ]
}

Tags

The brand's tags and tag folders, which group its prompts.

Tags and tag folders (list_tags)

list_tags. Read. Changes nothing.

The brand's tags and tag folders, as the dashboard's tag manager shows them. A tag is a label on prompts, used to segment the portfolio (by topic, product line, funnel stage); a prompt can carry several. Each tag row carries its id, its name, its folder (or null), how many of the brand's prompts carry it and whether it is a system tag. Every folder is listed in the answer whatever the page, with its order and how many tags it holds. Tags come in folder order, then by name, with tags in no folder last. Filter by folder or by text in the name; page with limit and cursor. Use it to find the tag ids that list_prompts, create_prompts and saved views take, and before creating tags so a name is not repeated.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
folderIdstringNoOnly tags in this folder, by id. A folder id the brand does not hold answers not_found.
searchstringNoOnly tags whose name contains this, ignoring case.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with folders, tags, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "folderId": "folder_1",
  "limit": 100,
  "search": "pricing"
}

Create tags (create_tags)

create_tags. Write. Most clients ask you before they run it. Team owners and admins only.

Creates up to 100 tags on the brand in one call, each optionally inside a folder and with a colour and a description. A new tag carries no prompts; put it on prompts with create_prompts, or in the dashboard. Tag names are unique per brand ignoring case: a name the brand already uses, or one repeated in the call, refuses the whole call with conflict and nothing is created. A folder id the brand does not hold refuses it with not_found. Only team owners and admins can create tags, as in the dashboard. Send an idempotencyKey to make a retry safe. Call list_tags first to see the names and folders in use.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
tagslist of objectYesThe tags to create.
tags[].colorstringNoThe tag's colour in the dashboard, as #rrggbb such as #6e79d6. Omit for the default.
tags[].descriptionstringNoWhat the tag is for, up to 500 characters.
tags[].folderIdstringNoThe folder to put the tag in, by the id list_tags returns. Omit for no folder.
tags[].namestringYesThe tag's name, unique on the brand ignoring case.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "tags-2026-11-05",
  "tags": [
    {
      "color": "#46a758",
      "folderId": "folder_1",
      "name": "Payroll"
    },
    {
      "description": "Invoices and receipts",
      "folderId": "folder_1",
      "name": "Invoicing"
    }
  ]
}

Change tags (update_tags)

update_tags. Write. Most clients ask you before they run it. Team owners and admins only.

Renames tags, moves them between folders, or changes their colour or description, for up to 100 tags in one call. Only the fields you send change. Every prompt keeps the tags it carries: a renamed tag is the same tag. Pass folderId null to take a tag out of its folder, and color or description null to clear it. A system tag cannot be renamed or moved (forbidden), though its colour and description can change; a new name the brand already uses, or one given twice in the call, is a conflict; a tag or folder id the brand does not hold is not_found. Any fault refuses the whole call and nothing changes. Sending a tag's current values changes nothing and is safe to repeat. Only team owners and admins can change tags, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
tagslist of objectYesThe tags to change, each named by id.
tags[].colorstring or nullNoThe tag's new colour as #rrggbb; null for the default. Omit to keep it.
tags[].descriptionstring or nullNoThe tag's new description; null to clear it. Omit to keep it.
tags[].folderIdstring or nullNoThe folder to move the tag into, by id; null takes it out of any folder. Omit to leave it where it is.
tags[].namestringNoThe tag's new name. Omit to keep its name.
tags[].tagIdstringYesThe tag to change, by the id list_tags returns.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "tags": [
    {
      "folderId": "folder_2",
      "tagId": "tag_1"
    },
    {
      "folderId": null,
      "tagId": "tag_2"
    },
    {
      "color": "#6e79d6",
      "description": "Pricing questions",
      "tagId": "tag_3"
    }
  ]
}

Delete tags (delete_tags)

delete_tags. Delete. Most clients ask you before they run it. Team owners and admins only.

Deletes up to 100 tags. The prompts that carried a tag keep being tracked and lose only that tag; the answer states, per tag, how many prompts lost it and how many saved views named it (a saved view skips a tag that no longer exists). This cannot be undone. A system tag cannot be deleted (forbidden), and a tag id the brand does not hold is not_found; either refuses the whole call and nothing is deleted. Only team owners and admins can delete tags, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
tagIdslist of stringYesThe tags to delete, by the ids list_tags returns.

Returns an object with deleted, replayed.

Example call

{
  "brandId": "brand_1",
  "tagIds": [
    "tag_1",
    "tag_2"
  ]
}

Create tag folders (create_tag_folders)

create_tag_folders. Write. Most clients ask you before they run it. Team owners and admins only.

Creates up to 100 tag folders on the brand in one call. A folder groups tags: inside one folder, filtering by two of its tags matches prompts carrying either; across folders, both must match. A new folder is empty and sorts after the existing ones unless you give an order. Folder names are unique per brand ignoring case: a name in use, or one repeated in the call, refuses the whole call with conflict and nothing is created. Only team owners and admins can create folders, as in the dashboard. Send an idempotencyKey to make a retry safe. Put tags in a folder with create_tags or update_tags.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
folderslist of objectYesThe folders to create.
folders[].namestringYesThe folder's name, unique on the brand ignoring case.
folders[].orderintegerNoThe folder's place in the list, lowest first. Omit to put it after the folders already there.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "folders": [
    {
      "name": "Funnel stage",
      "order": 0
    },
    {
      "name": "Regions",
      "order": 1
    }
  ],
  "idempotencyKey": "folders-2026-11-05"
}

Rename or reorder tag folders (update_tag_folders)

update_tag_folders. Write. Most clients ask you before they run it. Team owners and admins only.

Renames or reorders up to 100 tag folders in one call. The tags inside a folder stay in it. A system folder can be reordered but not renamed (forbidden); a new name the brand already uses, or one given twice in the call, is a conflict; a folder id the brand does not hold is not_found. Any fault refuses the whole call and nothing changes. Only team owners and admins can change folders, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
folderslist of objectYesThe folders to change, each named by id.
folders[].folderIdstringYesThe folder to change, by the id list_tags returns.
folders[].namestringNoThe folder's new name. Omit to keep its name.
folders[].orderintegerNoThe folder's new place in the list, lowest first.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "folders": [
    {
      "folderId": "folder_1",
      "name": "Products",
      "order": 2
    }
  ]
}

Delete tag folders (delete_tag_folders)

delete_tag_folders. Delete. Most clients ask you before they run it. Team owners and admins only.

Deletes up to 100 tag folders. The tags inside a folder are not deleted: they stay on their prompts and move to no folder, and the answer names them. This cannot be undone. A system folder cannot be deleted (forbidden), and a folder id the brand does not hold is not_found; either refuses the whole call and nothing is deleted. Only team owners and admins can delete folders, as in the dashboard. To delete the tags too, call delete_tags. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
folderIdslist of stringYesThe folders to delete, by the ids list_tags returns.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with deleted, replayed.

Example call

{
  "brandId": "brand_1",
  "folderIds": [
    "folder_1"
  ]
}

Saved views

Saved views: a market, tags and a query type saved under a name, applied to the visibility report.

Saved views (list_views)

list_views. Read. Changes nothing.

The brand's saved views: named filters the team set up in the dashboard to focus on one segment of the portfolio, such as one market and a few tags. Each view carries its id, its name and its filters exactly as get_visibility_report takes them (market, tagIds, queryType: spread them into its arguments), with the market and tags also named and how the view matches its tags. A view saved with no query type reads all prompts, and its filters say queryType all. The report matches tags by folder; a view whose several tags match by any or all reads a different set, and reportMatchesView says so. The filters are the view as the dashboard applies it: a tag the brand has since deleted is listed under missingTagIds and left out, a market with no tracked prompts is left out (market.measured false), and a view with nothing left says so in reportNote. Views come by name. Use it to report on the segments the team already uses, or before creating a view so a name is not repeated.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with views, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "limit": 100
}

Create saved views (create_views)

create_views. Write. Most clients ask you before they run it. Team owners and admins only.

Creates up to 50 saved views on the brand in one call, each a name plus a market, tags and a query type. The team sees them in the dashboard's Views menu on the visibility and prompts pages. A view must narrow something: a market, at least one tag, or a query type other than all. View names are unique per brand ignoring case: a name in use, or one repeated in the call, refuses the whole call with conflict and nothing is created. A tag id the brand does not hold is not_found; a market Heading does not measure is invalid_input. Only team owners and admins can create views, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
viewslist of objectYesThe views to create.
views[].marketstringNoThe market to narrow to, as a market tag such as en-GB. Omit for every market.
views[].namestringYesThe view's name, unique on the brand ignoring case.
views[].queryTypeone of all, branded, unbrandedNobranded, unbranded or all. Defaults to all.
views[].tagIdslist of stringNoTags to narrow to, by the ids list_tags returns; at most 30.
views[].tagModeone of facets, any, allNoHow the tags match: facets (the default) matches any tag inside one folder and needs every folder; any matches prompts carrying at least one of the tags; all needs every tag.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "views-2026-11-05",
  "views": [
    {
      "name": "Branded",
      "queryType": "branded"
    },
    {
      "name": "Payroll or invoicing",
      "tagIds": [
        "tag_1",
        "tag_2"
      ],
      "tagMode": "any"
    }
  ]
}

Change saved views (update_views)

update_views. Write. Most clients ask you before they run it. Team owners and admins only.

Renames saved views or changes their filters, for up to 50 views in one call. Only the fields you send change; tagIds replaces the view's whole tag list. Pass market null to drop the market filter. The view must still narrow something afterwards. A new name in use, or given twice in the call, is a conflict; a view or tag id the brand does not hold is not_found. Any fault refuses the whole call and nothing changes. Sending a view's current values changes nothing and is safe to repeat. Only team owners and admins can change views, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
viewslist of objectYesThe views to change, each named by id.
views[].marketstring or nullNoThe market to narrow to, as a market tag; null for every market. Omit to keep it.
views[].namestringNoThe view's new name. Omit to keep it.
views[].queryTypeone of all, branded, unbrandedNobranded, unbranded or all. Omit to keep it.
views[].tagIdslist of stringNoThe view's new tag list, replacing the old one; empty for no tag filter. Omit to keep it.
views[].tagModeone of facets, any, allNoHow the tags match: facets (the default) matches any tag inside one folder and needs every folder; any matches prompts carrying at least one of the tags; all needs every tag. Omit to keep it.
views[].viewIdstringYesThe view to change, by the id list_views returns.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "views": [
    {
      "market": null,
      "queryType": "unbranded",
      "tagIds": [],
      "viewId": "view_2"
    }
  ]
}

Delete saved views (delete_views)

delete_views. Delete. Most clients ask you before they run it. Team owners and admins only.

Deletes up to 50 saved views. A view is only a saved filter: deleting it changes no prompt, tag or figure, and the answer names each view with the filters it held. This cannot be undone. A view id the brand does not hold is not_found and refuses the whole call. Only team owners and admins can delete views, as in the dashboard. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
viewIdslist of stringYesThe views to delete, by the ids list_views returns.

Returns an object with deleted, replayed.

Example call

{
  "brandId": "brand_1",
  "viewIds": [
    "view_1"
  ]
}

Competitors

The competitors the brand is compared with, and the candidates waiting on a decision.

Tracked competitors (list_competitors)

list_competitors. Read. Changes nothing.

The brand's tracked competitors and the competitor candidates waiting on a decision. Each competitor carries its id, its name, the tracked name answers are matched on, its aliases, its own domains, when it started being tracked, and whether it is tracked for this brand or for every brand in the team. Every comparison figure (Mention share, the leaderboard, rank among tracked brands) is measured over this set, so it says what those figures mean. Each candidate is a business the competitor passes found in the brand's answers or market research: its id, name, domain, the check's verdict, confidence and evidence, where it came from, and how many answers named or cited it. Candidates the team rejected or the check judged not a competitor are left out unless includeNotCompetitors is true. This tool carries no visibility figures: for each competitor's share of answers and rank, call get_visibility_report with breakdown brand; for the answers that name it, list_answers with its id in competitorIds. Pages over the tracked competitors with limit and cursor; the candidates come whole on every page.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
includeNotCompetitorsbooleanNoWhen true, also list candidates the team rejected or the check judged not a direct competitor, so one can be accepted anyway. Defaults to false.
searchstringNoOnly competitors and candidates whose name, tracked name, alias or domain contains this, ignoring case.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with candidateCounts, candidates, competitors, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "includeNotCompetitors": true,
  "limit": 100,
  "search": "acme"
}

Add tracked competitors (create_competitors)

create_competitors. Write. Most clients ask you before they run it. Team owners and admins only.

Starts tracking up to 50 competitors for the brand in one call, as the dashboard's Add competitor does. Give each one's name, and optionally its own domain and the other names AI answers use for it. From then on Heading finds each one in the brand's answers: every comparison figure (Mention share, the leaderboard, rank among tracked brands) is measured over the tracked set, so adding competitors changes what those figures measure, and each addition is recorded as a measurement change that list_changes reports. Answers collected from then on are matched against each new competitor, and once the brand's first collection day has landed, that day's answers are matched at once as well. A name the brand already tracks, ignoring case, is listed under alreadyTracked and not changed. A candidate with the same name or domain is marked accepted. Only team owners and admins may add competitors. Send an idempotencyKey to make a retry safe. Call list_competitors first to see what is tracked and which candidates are waiting.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
competitorslist of objectYesThe competitors to start tracking.
competitors[].aliaseslist of stringNoOther names AI answers use for the competitor, such as a short form or an old name, at most 10. Each is matched word for word, ignoring case. No patterns.
competitors[].domainstringNoA domain of the competitor's own, bare, such as example.com: no scheme, no path. An answer citing it counts as citing the competitor.
competitors[].namestringYesThe competitor's name, as it writes it. Heading matches answers on it, word for word, ignoring case.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with alreadyTracked, created, notAdded, replayed.

Example call

{
  "brandId": "brand_1",
  "competitors": [
    {
      "aliases": [
        "Acme Ltd"
      ],
      "domain": "acme.com",
      "name": "Acme"
    },
    {
      "domain": "globex.co.uk",
      "name": "Globex"
    }
  ],
  "idempotencyKey": "competitors-2026-11-05"
}

Change tracked competitors (update_competitors)

update_competitors. Write. Most clients ask you before they run it. Team owners and admins only.

Changes up to 50 tracked competitors in one call, as the dashboard's competitor edit does: the name shown, the tracked name answers are matched on, the aliases and the domains. A field left out is not changed; aliases and domains replace the whole list. A changed tracked name, alias or domain changes which answers count as naming or citing the competitor from the next collected answer; answers already collected keep the match they were given. A tracked name another competitor of the brand already uses, ignoring case, is refused. Every competitor is checked before anything is written: an id the brand does not track, or one tracked for every brand in the team, refuses the whole call. Only team owners and admins may change competitors. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
competitorslist of objectYesThe competitors to change, each with the fields to change.
competitors[].aliaseslist of stringNoThe whole alias list, replacing the current one, at most 10. Send [] to clear it. Omit to keep it.
competitors[].competitorIdstringYesThe competitor to change, by the id list_competitors returns.
competitors[].domainslist of stringNoThe whole domain list, replacing the current one, bare, such as example.com. Send [] to clear it. Omit to keep it.
competitors[].namestringNoThe name the dashboard shows. Omit to keep it.
competitors[].trackedNamestringNoThe name answers are matched on, word for word, ignoring case. Omit to keep it.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "competitors": [
    {
      "competitorId": "comp_1",
      "domains": [
        "acme.com",
        "acme.co.uk"
      ],
      "name": "Acme",
      "trackedName": "Acme Inc"
    }
  ],
  "idempotencyKey": "rename-acme-1"
}

Stop tracking competitors (delete_competitors)

delete_competitors. Delete. Most clients ask you before they run it. Team owners and admins only.

Stops tracking up to 50 competitors of the brand in one call, as the dashboard's competitor removal does. This deletes each competitor and every record of it in the brand's collected answers (its mentions, ranks and daily figures), so its history cannot be read again, even if it is added back. Every comparison figure is measured over the tracked set, so the removal is recorded as a measurement change that list_changes reports, and its candidate is marked rejected so the competitor passes do not propose it again. Every id is checked before anything is removed: an id the brand does not track, or a competitor tracked for every brand in the team, refuses the whole call. Only team owners and admins may remove competitors. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
competitorIdslist of stringYesThe competitors to stop tracking, by the ids list_competitors returns.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with deleted, replayed.

Example call

{
  "brandId": "brand_1",
  "competitorIds": [
    "comp_1",
    "comp_2"
  ],
  "idempotencyKey": "cleanup-2026-11-05"
}

Decide competitor candidates (update_competitor_candidates)

update_competitor_candidates. Write. Most clients ask you before they run it. Team owners and admins only.

Accepts or rejects up to 50 competitor candidates in one call, as the dashboard's candidate review does. Accepting starts tracking the candidate as a competitor, under the name the check found or the name you give, with its domain and aliases; it can accept a candidate that was rejected or judged not a competitor. Accepting changes what every comparison figure measures and is recorded as a measurement change that list_changes reports. Rejecting records the team's decision, tracks nothing and keeps the competitor passes from proposing it again. A candidate already tracked is listed under alreadyTracked; to stop tracking one, use delete_competitors. A candidate whose name the brand already tracks under another competitor is listed under refused, with the reason. Every id is checked before anything is written: an id the brand does not hold refuses the whole call. Only team owners and admins may decide candidates. Send an idempotencyKey to make a retry safe. Take the ids from list_competitors.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
acceptlist of objectNoCandidates to start tracking as competitors.
accept[].aliaseslist of stringNoThe aliases to track it with, replacing the check's, at most 10. Omit to keep the check's.
accept[].candidateIdstringYesThe candidate to accept, by the id list_competitors returns.
accept[].namestringNoThe name to track it under. Omit for the name the check found; a candidate with no name needs one.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
rejectlist of stringNoCandidates to reject, by id.

Returns an object with accepted, alreadyTracked, refused, rejected, replayed.

Example call

{
  "accept": [
    {
      "aliases": [
        "Acme Ltd"
      ],
      "candidateId": "cand_1",
      "name": "Acme"
    }
  ],
  "brandId": "brand_1",
  "idempotencyKey": "review-2026-11-05",
  "reject": [
    "cand_2",
    "cand_3"
  ]
}

Reports

The measured figures, computed by the same code as the dashboard pages. See Metrics for what each figure means.

Visibility report (get_visibility_report)

get_visibility_report. Read. Changes nothing.

The brand's visibility in AI answers, as the visibility page measures it, over a UTC day range: Mentioned %, Cited %, Mention share, Citation share, average mentions, average citations, responses, average rank among tracked brands and average sentiment. One call answers one question: no breakdown gives a summary; breakdown date gives a series; breakdown brand gives the leaderboard of the brand and its tracked competitors; platform, prompt, tag and market split the figures; up to three combine (such as brand and platform). Filter by tags (OR within a tag folder, AND across folders, as the page does), market, query type, platforms and prompts. Set compare for the previous period of the same length: every row then carries its previous figures and its change. Defaults: the last 28 days and unbranded prompts, which is what the visibility page opens on. A figure Heading did not measure is null with a reason, never zero. A range that opens before daily tracking began is clamped, and notes say so; a brand on no paid plan has one as-of day.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
startDaystringNoFirst UTC day of the range (inclusive), YYYY-MM-DD. Defaults to 27 days before endDay. A day before daily tracking began is moved to that day, with a note; a day after the range's last day answers invalid_input.
endDaystringNoLast UTC day of the range (inclusive), YYYY-MM-DD. Defaults to, and may not pass, the last day the brand can be measured on: today for a brand on a paid plan, its as-of day when it has never paid, its last collected day when its plan lapsed.
breakdownlist of one of date, platform, prompt, tag, market, brandNoSplit the figures by date, platform, prompt, tag, market or brand, alone or up to three combined, in nesting order. Omit for the summary alone. brand lists the brand and every tracked competitor (the leaderboard). date cannot combine with compare, and is refused for a brand with one as-of day. A breakdown that would make more than 20,000 rows is refused: narrow the range, add a filter or drop a breakdown.
intervalone of day, week, monthNoWith a date breakdown: day (default), week (Monday to Sunday) or month.
queryTypeone of all, branded, unbrandedNounbranded (prompts that do not name the brand, the default and what the visibility page opens on), branded, or all.
tagIdslist of stringNoOnly prompts carrying these tags, by id: any of the tags in one folder, and every folder named. A tag id the brand does not hold answers not_found.
marketstringNoOnly prompts asked in this market, as a market tag such as en-GB. Omit for every market.
platformslist of stringNoOnly answers from these AI platforms, by id (chatgpt, perplexity, ai_overview, ai_mode, gemini, copilot, claude, gpt_search). Omit for every platform.
promptIdslist of stringNoOnly these tracked prompts, by the ids list_prompts returns. A prompt that is not active answers not_found: figures are measured over active prompts.
comparebooleanNotrue to measure the comparison period too, so every row carries previous and change. Off by default.
comparePeriodone of prev_period, prev_month, prev_quarterNoThe comparison period, always as many days as the range: prev_period (the days just before the range, the default and the page's comparison), prev_month or prev_quarter (the same days one or three months back; one that would overlap the range falls back to prev_period, with a note). Implies compare.
sortByone of mentionedPct, citedPct, mentionShare, citationShare, avgMentions, avgCitations, responses, averageRank, avgSentiment, nameNoSort rows by this metric, highest first (averageRank lowest first), or by name. Defaults to mentionedPct. With a date breakdown, rows are in date order first.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with breakdown, comparison, filters, interval, notes, range, rows, summary, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "breakdown": [
    "brand",
    "platform"
  ],
  "comparePeriod": "prev_month",
  "limit": 100,
  "market": "en-GB",
  "platforms": [
    "chatgpt",
    "perplexity"
  ],
  "promptIds": [
    "prompt_1"
  ],
  "tagIds": [
    "tag_1"
  ]
}

Objective measure (get_objective_measure)

get_objective_measure. Read. Changes nothing.

The figure a metric key result is judged on, exactly as the objective page charts it: visibility, the share of one UTC day's tracked answers that mention the brand, for one scope (query type, and optionally a market and tags), on the latest measured day by the page's own rule (for a brand whose plan lapsed, its last collected day). Read it to set a baseline before create_objectives or update_objectives: write its reading's value and day into measureConfig.baseline and its scope into measureConfig.scope, unchanged, so the key result is judged on the same measure over the same prompts. Read it again with a key result's own scope and its baseline day as sinceDay to verify that key result: the reading is then the key result's current figure on the objective page. It is a different figure from get_visibility_report, whose range figures pool many days; never compare one with the other. A scope with no measured day answers a null reading with a reason, never zero.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
queryTypeone of all, branded, unbrandedYesWhich prompts: unbranded (prompts that do not name the brand), branded, or all. Required, so the scope a key result is judged on is always stated.
marketstringNoOnly prompts asked in this market, as a market tag such as en-GB. Omit for every market.
tagIdslist of stringNoOnly prompts carrying these tags, by id: any of the tags in one folder, and every folder named.
sinceDaystringNoWhen verifying, the key result's baseline day (YYYY-MM-DD): only days from it on count, as on the objective page, so no day measured since answers a null reading. Omit when setting a baseline.

Returns an object with measure, reading, reason, scope.

Example call

{
  "brandId": "brand_1",
  "market": "en-GB",
  "queryType": "all",
  "sinceDay": "2026-10-01",
  "tagIds": [
    "3f0c8a8e-2d4b-4c5e-9a7b-1e2f3a4b5c6d"
  ]
}

Perception report (get_perception_report)

get_perception_report. Read. Changes nothing.

How AI depicts the brand, as the Perception page measures it, over a UTC day range. Perception is measured on dimensions: ten spine qualities every brand shares plus qualities generated for the brand's category, and every tracked brand is judged on the same dimensions, so a dimension a rival holds and the brand does not is visible. The summary is the brand's own: overall sentiment, focused dimensions led and the strongest quality. breakdown dimension lists every dimension with the brand's rate, the best compared competitor's rate and the gap, widest deficit first, as the page ranks them; breakdown brand lists the brand and every tracked competitor with their own sentiment and focus figures (the page's brand switcher); both together give each brand's rate on each dimension. Focused dimensions are flagged, never filtered: every dimension is readable. Retired dimensions are listed with the day they retired and measured over the days before it. A brand with fewer than 10 assessed answers reads null with the reason not_measured, never 0%. Filter by market, query type, platforms, tags and prompts; set compare for the previous period of the same length. Defaults: the last 28 days and unbranded prompts, as the page opens. It answers what AI says about the brand; get_visibility_report answers how often AI names it.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
startDaystringNoFirst UTC day of the range (inclusive), YYYY-MM-DD. Defaults to 27 days before endDay. A day before daily tracking began is moved to that day, with a note.
endDaystringNoLast UTC day of the range (inclusive), YYYY-MM-DD. Defaults to, and may not pass, the last day the brand can be measured on: today on a paid plan, its as-of day when it has never paid, its last collected day when its plan lapsed.
breakdownlist of one of dimension, brandNodimension, brand, or both in nesting order. Omit for the summary alone. brand lists the brand and up to 49 tracked competitors, in tracked order; a note says when more were left out.
queryTypeone of all, branded, unbrandedNounbranded (prompts that do not name the brand, the default and what the page opens on), branded, or all.
tagIdslist of stringNoOnly prompts carrying these tags, by id: any of the tags in one folder, and every folder named. A tag id the brand does not hold answers not_found.
marketstringNoOnly prompts asked in this market, as a market tag such as en-GB. Omit for every market.
platformslist of stringNoOnly answers from these AI platforms, by id (chatgpt, perplexity, ai_overview, ai_mode, gemini, copilot, claude, gpt_search). Omit for every platform.
promptIdslist of stringNoOnly these tracked prompts, by the ids list_prompts returns. A prompt that is not active answers not_found.
comparebooleanNotrue to measure the comparison period too, so every row carries previous and change. Off by default.
comparePeriodone of prev_period, prev_month, prev_quarterNoprev_period (the days just before the range, the default), prev_month or prev_quarter (the same days one or three months back; one that would overlap the range falls back to prev_period, with a note). Implies compare.
sortByone of gap, rate, sentiment, nameNogap (the default, the page's order: dimensions widest deficit first, retired last; brands the brand first, then competitors in tracked order), rate (dimensions by rate, highest first), sentiment (brands by sentimentPct, highest first) or name.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with breakdown, comparison, dimensions, filters, notes, range, rows, summary, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "breakdown": [
    "dimension"
  ],
  "comparePeriod": "prev_month",
  "limit": 100,
  "market": "en-GB",
  "platforms": [
    "chatgpt",
    "perplexity"
  ],
  "promptIds": [
    "prompt_1"
  ],
  "queryType": "all",
  "sortBy": "rate",
  "tagIds": [
    "tag_1"
  ]
}

Sources report (get_sources_report)

get_sources_report. Read. Changes nothing.

The sources AI platforms cite in their answers to the brand's tracked prompts, as the Sources page measures them, over a UTC day range. level domain (the default) lists cited domains with Used % and average citations; level url lists cited pages with Citation % and Retrieval %. Every row says whether the domain is the brand's own, and carries two flags over the whole range: gap (cited in answers that name a tracked competitor and in none that name the brand, over this report's own answers: co-citation in answers, never a claim about what the source's pages say, and not the Sources page's Gaps tab figure) and rising (its citation rate rose against the period just before). Filter by tags (OR within a tag folder, AND across folders), market and query type; at level url also by one platform and page format. Break down by date (domains by month; pages by day, week or month) or, at level url, by each platform the brand tracks. Set compare for the previous period of the same length. Defaults: the last 28 days and unbranded prompts, as the Sources page opens. A cited source is a page an AI answer named as its source: not a backlink, not traffic. A figure Heading did not measure is null with a reason, never zero; read the notes before stating a figure.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
levelone of domain, urlNodomain (the default) for one row per cited domain, url for one row per cited page.
startDaystringNoFirst UTC day of the range (inclusive), YYYY-MM-DD. Defaults to 27 days before endDay. A day before daily tracking began is moved to that day, with a note.
endDaystringNoLast UTC day of the range (inclusive), YYYY-MM-DD. Defaults to, and may not pass, the last day the brand can be measured on: today for a brand on a paid plan, its as-of day when it has never paid, its last collected day when its plan lapsed.
breakdownlist of one of date, platformNoSplit the rows by date, or at level url by each platform the brand tracks, or both, in nesting order. date cannot combine with compare, and is refused for a brand with one as-of day. A breakdown that would read more than 31 periods and platforms is refused: narrow the range or use a longer interval.
intervalone of day, week, monthNoWith a date breakdown: month (the default), week (Monday to Sunday) or day. Domains are read by month only: their figures are kept per month.
queryTypeone of all, branded, unbrandedNounbranded (prompts that do not name the brand, the default and what the Sources page opens on), branded, or all.
tagIdslist of stringNoOnly prompts carrying these tags, by id: any of the tags in one folder, and every folder named. A tag id the brand does not hold answers not_found.
marketstringNoOnly prompts asked in this market, as a market tag such as en-GB. Omit for every market.
platformstringNoLevel url only: only answers from this AI platform, by id (chatgpt, perplexity, ai_overview, ai_mode, gemini, copilot, claude, gpt_search). Domain figures cannot be split by platform.
domainTypeone of own, ugc, corporate, editorialNoOnly domains of this type, such as own or ugc.
formatone of listicle, article, how_to, product, homepage, forum, video, otherNoLevel url only: only pages of this format, such as video.
searchstringNoOnly domains (or page addresses at level url) containing this text.
comparebooleanNotrue to measure the comparison period too, so every row carries previous and change. Off by default.
comparePeriodone of prev_period, prev_month, prev_quarterNoThe comparison period, always as many days as the range: prev_period (the days just before the range, the default and the Sources page's comparison), prev_month or prev_quarter (one that would overlap the range falls back to prev_period, with a note). Implies compare.
sortByone of usedPercent, avgCitations, citationRate, retrievalRate, citationCount, retrievalCount, nameNoSort rows by this metric, highest first, or by name; a metric the level does not carry sorts last. Defaults to usedPercent for domains and citationRate for pages, as the Sources page sorts. With a date breakdown, rows are in date order first.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with breakdown, comparison, counts, filters, interval, level, notes, range, rows, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "breakdown": [
    "date"
  ],
  "interval": "month"
}

Answers

The AI answers Heading collected for the brand's tracked prompts. Every score is computed from these, so this is where a disagreement gets settled.

AI answers (list_answers)

list_answers. Read. Changes nothing.

The AI platform answers collected for the brand's active prompts, newest first, as the visibility page lists them: the evidence every visibility figure is computed from. Each row carries the answer's id, the prompt it answers (id and text), the platform, the UTC day it counts on, whether it names the brand, the brand's rank among tracked brands (null when not named), the brand's sentiment (null when not scored), the tracked competitors it names with their ranks, how many sources it cites, and a short plain-text excerpt. Filter by UTC day range (default the last 28 days, clamped to the day daily tracking began), platforms, prompts, market, tags, query type, the tracked competitors named, or to answers that name the brand. Use it for "what is ChatGPT saying about us", to find the answers behind a score that moved, or to pick answers to read in full with get_answer. Page with limit and cursor; a cursor only works with the filters it came from.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
competitorIdslist of stringNoOnly answers that name at least one of these tracked competitors, by id. A competitor id the brand does not track answers not_found.
endDaystringNoThe last UTC day to read (YYYY-MM-DD), inclusive. Defaults to today.
marketstringNoOnly answers to prompts asked in this market, as a market tag such as en-GB. Omit for every market.
mentionedOnlybooleanNoWhen true, only answers that name the brand.
platformslist of one of chatgpt, perplexity, ai_overview, ai_mode, gemini, copilotNoOnly answers from these AI platforms. Omit for every platform.
promptIdslist of stringNoOnly answers to these prompts, by the ids list_prompts returns, whatever their status, so a paused prompt's earlier answers can be read. A prompt the brand has removed is accepted too, for its answers from days before the removal. Omit for every active prompt. A prompt id the brand does not hold answers not_found.
queryTypeone of all, branded, unbrandedNobranded (prompts that name the brand), unbranded (prompts that do not) or all. Defaults to all.
startDaystringNoThe first UTC day to read (YYYY-MM-DD), inclusive. Defaults to 27 days before endDay. Days before daily tracking began are not read, and the answer says so.
tagIdslist of stringNoOnly answers to prompts carrying these tags, by id: any of the tags in one folder, and every folder named. A tag id the brand does not hold answers not_found.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with answers, range, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "competitorIds": [
    "competitor_1"
  ],
  "endDay": "2026-11-04",
  "limit": 100,
  "market": "en-GB",
  "mentionedOnly": true,
  "platforms": [
    "chatgpt",
    "perplexity"
  ],
  "promptIds": [
    "prompt_1"
  ],
  "queryType": "unbranded",
  "startDay": "2026-10-08",
  "tagIds": [
    "tag_1"
  ]
}

AI answer (get_answer)

get_answer. Read. Changes nothing.

One collected AI answer in full, by the id list_answers returns: the full answer text exactly as the platform gave it (markdown, never shortened), the prompt it answers, the platform, the UTC day and time it was collected and how it was fetched, every source it cites (URL, domain, title where the platform gave one, and whether the domain is one of the brand's sites), and every tracked brand it names (the brand and its tracked competitors) with each one's rank among tracked brands and sentiment. Use it to quote an answer, to see who an answer recommends instead of the brand, or to check which pages it cites. An answer to a prompt this brand does not track answers not_found.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
answerIdstringYesThe answer to read, by the id list_answers returns.

Returns an object with answer, brand, citations, mentions, prompt.

Example call

{
  "answerId": "answer_1",
  "brandId": "brand_1"
}

Measurement changes

What changed what the scores measure. When a figure moved, read this first to tell a change in the measurement from a change in performance.

Measurement changes (list_changes)

list_changes. Read. Changes nothing.

What changed what the brand's scores measure, one entry per UTC day, newest day first: prompts added, tracked, reactivated, paused, removed, suggested or rejected (with counts and a sample of each), AI platforms added to or removed from the tracked set, and competitors added to or removed from the tracked set (by name). Each day is netted, so a change undone the same day is not listed, and states the active prompt count at the end of the day, which is the set that day's visibility is measured over. Visibility is measured over the active prompts and the tracked platforms, and every comparison over the tracked competitors, so a score that moves on one of these days may have moved because the measurement changed, not the brand's performance. Read it before explaining any movement in a score; the days line up with the change markers on the visibility chart. A competitor removed and added back on one day nets to nothing here, although the removal dropped its earlier mention history. Covers the last 30 days by default; name startDay and endDay for another range (up to 366 days). The default range ends today, so when paging with cursor across midnight UTC, pass the same endDay on every page. Days with no change are not listed.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
endDaystringNoThe last UTC day (YYYY-MM-DD) to cover, inclusive. Defaults to today; pass it explicitly when paging, so every page covers the same days.
startDaystringNoThe first UTC day (YYYY-MM-DD) to cover, inclusive. Defaults to 29 days before endDay, so 30 days in all.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with changes, range, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "endDay": "2026-10-31",
  "limit": 50,
  "startDay": "2026-10-01"
}

Prompt research

Prompt research: candidate questions from four sources, triaged before anyone tracks them. Two sources spend credits; see Writes and limits.

Prompt candidates (list_prompt_candidates)

list_prompt_candidates. Read. Changes nothing.

The brand's prompt research list for one market: candidate questions that sources offered the brand (the index of AI answers, Search Console, questions generated from the brand profile, an uploaded list) before anyone tracks them. Each candidate carries its id, exact text, status, topic, the judge's relevance, its duplicate group (other wordings of the same question, led by the strongest), its flags, its query type, every source with the figures it gave and the UTC day it gave them, the candidates a converted question was written from, its target page and the team's note. A null figure means that source gave no figure, never zero. Omit view for the Inbox and the Shortlist together, the questions the team has not decided on; tracked lists the candidates already added. The answer also states every market's counts, the topics in use, how many candidates carry each flag, the market's recent pulls (a pending pull lands here when it finishes), and the team's AI & data credits with what each source costs. Order is by demand, the strongest AI search volume first. A candidate is NOT a tracked prompt (list_prompts lists those). Use it before pulling, moving or adding candidates, to find their ids.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
marketstringNoThe market as a market tag such as en-GB. Omit for the market the workbench opens on: the brand's primary market, or the first one holding candidates.
topicstringNoOnly candidates in this topic, matched ignoring case, as the topics list names it.
viewone of inbox, shortlist, tracked, dismissed, usedNoinbox, shortlist, tracked (added as prompts), dismissed or used (rewritten into another question). Omit for the Inbox and the Shortlist together.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with candidates, credits, listed, market, markets, pulls, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "limit": 50,
  "market": "en-GB",
  "topic": "Pricing",
  "view": "shortlist"
}

Pull quote (get_pull_quote)

get_pull_quote. Read. Changes nothing.

What a pull_prompt_candidates call with these arguments would cost now, in credits from the team's AI & data credits, and whether the balance lets it start. Changes nothing and spends nothing. An ai_index pull reads the index of AI answers for about 30 credits (state fetch), or costs nothing when anyone searched that topic in the last 30 days (cached) or a read of it is already running (in_flight). A generate run is priced by how many it writes (priced: 2 for 10, 3 for 20, 6 for 40). search_console and your_list are free. Tell the person the cost, then pass credits as acceptQuote to pull_prompt_candidates. A quote is not held: the pull checks the price again and refuses as conflict if it went up.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
count10 or 20 or 40Nogenerate only: how many to write. Defaults to 20.
marketstringNoThe market to pull for, as a market tag such as en-GB. Defaults to the brand's primary market.
sourceone of ai_index, search_console, generate, your_listYesThe source the pull would read.
topicstringNoai_index only, required there: the topic to look up.

Returns an object with balance, canSpend, cost, credits, market, source, state.

Example call

{
  "brandId": "brand_1",
  "source": "search_console"
}

Pull prompt candidates (pull_prompt_candidates)

pull_prompt_candidates. Write. Most clients ask you before they run it. Any team member with access to the brand. Can spend credits.

Pulls candidate questions into the brand's prompt research list for one market, from one source: ai_index (questions people ask AI assistants about a topic, from the index of AI answers), search_console (the connected site's own search queries, free), generate (questions written from the brand profile, aimed at chosen angles) or your_list (lines you supply, free). Nothing is tracked: candidates land in the Inbox for the team to review, and a text already listed or tracked is merged, never doubled. The AI index and Generate are paid from the team's AI & data credits: call get_pull_quote first, tell the person the cost (an AI index read uses about 30 credits, nothing when anyone searched that topic in the last 30 days; a Generate run of 20 uses about 3), and pass that number as acceptQuote: it is the most this call may spend. A pull that would now cost more is refused as conflict with the new price, and nothing is spent while the balance is not above zero. An AI index read is charged when the index answers, never if the call to the index fails; a Generate run is charged when its candidates land. Both usually land within minutes: the answer says pending, and list_prompt_candidates shows the candidates when they land.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
acceptQuoteintegerNoRequired for ai_index and generate: the credits get_pull_quote answered for this pull, the most this call may spend. Ignored for the free sources.
angleslist of one of differentiators, buying_path, comparisons, problemsNogenerate only, required there: what to write about. differentiators, buying_path (where to buy, price, eligibility), comparisons, problems (what it solves).
count10 or 20 or 40Nogenerate only: how many to write, 10, 20, 40. Defaults to 20.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments pulls once and returns the first answer, for a day.
lineslist of stringNoyour_list only, required there: the questions to add as candidates, one per line.
listNamestringNoyour_list only: the name the pull is shown under. Defaults to Agent list.
marketstringNoThe market to pull for, as a market tag such as en-GB. Defaults to the brand's primary market. Any market Heading supports may be added.
minImpressionsintegerNosearch_console only: the fewest impressions a query needs. Defaults to 50.
questionsFirstbooleanNosearch_console only: put queries phrased as questions first. Defaults to true.
rangeone of last_28_days, last_3_months, last_6_months, last_12_monthsNosearch_console only: how far back to read. Defaults to last_3_months.
sourceone of ai_index, search_console, generate, your_listYesWhere to pull candidates from.
styleone of questions, mix, short_queriesNogenerate only: questions (natural questions), short_queries (search style) or mix. Defaults to questions.
topicstringNoai_index only, required there: the topic to look up, such as running shoes for flat feet.

Returns an object with balance, candidates, market, note, pull, replayed, source, spentCredits.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "list-2026-11-05",
  "lines": [
    "which crm suits a five person team"
  ],
  "listName": "Sales call questions",
  "source": "your_list"
}

Move prompt candidates (move_prompt_candidates)

move_prompt_candidates. Write. Most clients ask you before they run it. Any team member with access to the brand.

Moves candidates between the brand's Inbox, Shortlist and Dismissed lists, as the workbench's triage buttons do: shortlist (inbox to shortlist), dismiss (inbox or shortlist to dismissed), restore (dismissed to inbox) and each move back. Name the list the candidates are in now as from, by what list_prompt_candidates returned: only candidates still there move, so a teammate's newer move is never overwritten, and every candidate that did not move is answered with the list it is in now (null when the brand holds no candidate with that id). A dismissed candidate is never offered again by a later pull. Nothing here is tracked: add_prompt_candidates adds shortlisted ones to the portfolio. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
candidateIdslist of stringYesThe candidates to move, by the ids list_prompt_candidates returns.
fromone of inbox, shortlist, dismissedYesThe list the candidates are in now.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments moves once and returns the first answer, for a day.
toone of inbox, shortlist, dismissedYesThe list to move them to. Must differ from from.

Returns an object with moved, notMoved, replayed.

Example call

{
  "brandId": "brand_1",
  "candidateIds": [
    "cand_3"
  ],
  "from": "dismissed",
  "idempotencyKey": "restore-cand-3",
  "to": "inbox"
}

Add prompt candidates (add_prompt_candidates)

add_prompt_candidates. Write. Most clients ask you before they run it. Any team member with access to the brand.

Adds shortlisted candidates in one market to the brand's tracked prompts, as the workbench's Add to portfolio does, so each prompt keeps where it came from: its candidate, its sources and figures, its query type, its target page (checked against the brand's sites again) and its topic as a tag. New prompts are active and counted in every visibility figure from their first collection day, so this changes what the figures measure. Only candidates on the Shortlist are added, in the Shortlist's order (strongest demand first) until the prompt quota is full; the rest stay on the Shortlist and come back under didNotFit, never as an error. A text the brand already tracks is linked to that prompt and takes no slot. A candidate a teammate moved off the Shortlist first is left where it is and listed under notOnShortlist. Every addition is recorded in the prompt changelog under the person the call acts as. Move candidates to the Shortlist first with move_prompt_candidates; use create_prompts only for questions that are not candidates.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
candidateIdslist of stringYesThe shortlisted candidates to add, by the ids list_prompt_candidates returns.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments adds once and returns the first answer, for a day.
marketstringYesThe candidates' market, as a market tag such as en-GB.

Returns an object with added, alreadyTracked, collection, didNotFit, filedAsAdded, market, notOnShortlist, quota, replayed, targetPagesLeftOff.

Example call

{
  "brandId": "brand_1",
  "candidateIds": [
    "cand_1",
    "cand_2"
  ],
  "idempotencyKey": "add-shortlist-2026-11-05",
  "market": "de-DE"
}

Context

The brand's context documents: the facts Heading's own pipelines read about the business, and how well Heading understands it.

Context documents (list_context_docs)

list_context_docs. Read. Changes nothing.

The brand's Context tree: the folders and markdown documents the team keeps about the business, which Heading's own prompt generation, perception and setup read for brand facts. Each row carries the item's id, title, kind (folder or document), the folder it sits in, its position among that folder's items (the order the Context page shows, and the index move_context_docs takes), its depth, its version, when it last changed and a document's length; rows come in tree order, each folder followed by what it holds. Bodies are not included: get_context_doc reads one in full. The answer also carries Confidence, how well Heading understands the business as last assessed from these documents: a score and band, and per dimension (what they sell, audience, positioning, competitors, geography, recognition) the coverage grade with the questions and sections that would deepen it, weakest first. Confidence is read from the last assessment and never computed here, so it is null with a reason when nothing is assessed, and a document written in this session does not move it straight away. Read this before writing context, so you change an existing document rather than add a near-copy.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with confidence, counts, docs, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "limit": 100
}

Context document (get_context_doc)

get_context_doc. Read. Changes nothing.

One context document in full: its title, its whole markdown body with no cap, its version, when it was created and last changed, and its path (the folders it sits in, top level first). Take the id from list_context_docs. Send the version it answers with update_context_docs, so an edit someone else made since is never overwritten. A folder id answers invalid_input, since a folder has no body; list_context_docs lists what it holds.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
docIdstringYesThe item's id, as list_context_docs returns it.

Returns an object with body, createdAt, id, parentId, path, title, updatedAt, version.

Example call

{
  "brandId": "brand_1",
  "docId": "doc_1"
}

Add context documents (create_context_docs)

create_context_docs. Write. Most clients ask you before they run it. Any team member with access to the brand.

Adds up to 50 folders or documents to the brand's Context tree in one call. Give each a title, its kind (document unless you say folder), the folder to put it in (a folder id from list_context_docs, or null for the top level) and, for a document, its markdown body. Each lands last in its folder. The bodies in one call may total at most 2,000,000 characters; send more across several calls. The call is all or nothing: a folder id this brand does not hold, or a body on a folder, refuses the whole call and nothing is added. A document with the same title as an existing one is a second document, not an edit: to change what a document says, use update_context_docs. Heading's own pipelines read these documents for brand facts, and a new document with a body marks Confidence for a fresh assessment. Records the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
docslist of objectYesThe items to add, in the order to add them.
docs[].bodystringNoA document's body, as markdown. Leave it out for a folder, or for an empty document.
docs[].kindone of folder, documentNodocument (the default) or folder.
docs[].parentIdstring or nullNoThe folder it sits in, by id from list_context_docs; null for the top level of the tree.
docs[].titlestringYesThe title, as the Context page shows it.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "docs": [
    {
      "kind": "folder",
      "parentId": null,
      "title": "Competitors"
    },
    {
      "body": "## Main rivals\n\n- **Acme Payroll**",
      "kind": "document",
      "parentId": "folder_1",
      "title": "Rival notes"
    }
  ],
  "idempotencyKey": "context-2026-11-05-1"
}

Edit context documents (update_context_docs)

update_context_docs. Write. Most clients ask you before they run it. Any team member with access to the brand.

Changes the title or the body of up to 50 context items in one call. Each entry names the item, the version you read it at (from list_context_docs or get_context_doc) and what to change: a title, a body (documents only), or both. The body you send replaces the whole body, so read the document with get_context_doc first and send it back with your change in place. The bodies in one call may total at most 2,000,000 characters. An item someone changed since you read it refuses the whole call with conflict, naming its current version: read it again and apply your change to that. The call is all or nothing, so a stale or unknown item writes nothing. Each item changed gets a new version. A changed body marks Confidence for a fresh assessment. To move an item use move_context_docs. Records the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
docslist of objectYesThe edits, one entry per item; each item at most once.
docs[].bodystringNoThe new body, as markdown, replacing the whole body. Documents only.
docs[].docIdstringYesThe item's id, as list_context_docs returns it.
docs[].titlestringNoThe new title.
docs[].versionintegerYesThe version you read the item at. An older version than the item holds now refuses the call.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "docs": [
    {
      "body": "## Who buys\n\n- **Practice managers** at firms of 5 to 50",
      "docId": "doc_1",
      "version": 3
    }
  ],
  "idempotencyKey": "context-edit-2026-11-05-1"
}

Move context documents (move_context_docs)

move_context_docs. Write. Most clients ask you before they run it. Any team member with access to the brand.

Moves up to 50 context items to a folder and a place in it, in one call, as dragging them on the Context page would. Each entry names the item, the folder to move it into (a folder id, or null for the top level) and its index among that folder's items, from 0 (an index past the end puts it last). Moves apply in the order sent, each seeing the tree the moves before it left. A folder cannot go inside itself or anything under it. The call is all or nothing: an unknown item or folder, or a move that would put a folder inside itself, refuses the whole call. A move changes no text, so it needs no version and gives none. Records the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
moveslist of objectYesThe moves, in the order to make them; each item at most once.
moves[].docIdstringYesThe item's id, as list_context_docs returns it.
moves[].indexintegerYesIts place among the destination folder's items, from 0, in the order list_context_docs shows them.
moves[].parentIdstring or nullYesThe folder it sits in, by id from list_context_docs; null for the top level of the tree.

Returns an object with moved, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "context-move-2026-11-05-1",
  "moves": [
    {
      "docId": "doc_2",
      "index": 3,
      "parentId": null
    }
  ]
}

Delete context documents (delete_context_docs)

delete_context_docs. Delete. Most clients ask you before they run it. Any team member with access to the brand.

Deletes up to 50 context items for good. Deleting a folder deletes everything in it, at any depth; the answer says, for each item, how many documents and folders went with it. There is no undo, and Heading's own pipelines stop reading the deleted documents at once, so list what a folder holds with list_context_docs before deleting it and confirm with the person. The call is all or nothing: an id this brand does not hold refuses the whole call and nothing is deleted. An item that sits inside a folder deleted in the same call is counted once: on its own row when it comes first, otherwise in the folder's counts. Records the person the call acts as. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
docIdslist of stringYesThe items to delete, by id.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with deleted, replayed.

Example call

{
  "brandId": "brand_1",
  "docIds": [
    "folder_1",
    "doc_2"
  ],
  "idempotencyKey": "context-delete-2026-11-05-1"
}

Tasks, plans and objectives

The task board, its plans, and the brand's objectives.

Tasks and plans (list_tasks)

list_tasks. Read. Changes nothing.

The brand's task board and its live plans, with the ids the task, plan and comment tools take. Each task row carries its id, title, full description, status (backlog, todo, in_progress, done, canceled), priority, assignee (name and email), due day, plan, labels and when it was created, changed and completed. The board's own scope: every review-accepted task on a live plan or with no plan, as the Tasks page shows it. The answer also states how many tasks the board holds in each status and lists every plan that is not archived, with its status, the objective it serves and its open and total task counts, whatever the filters. Filter tasks by status, plan, objective (tasks on plans serving it), assignee or title; page with limit and cursor. Read it before creating work, so an open task that already covers a fix is updated with update_tasks rather than created again.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
assigneestringNoOnly tasks assigned to this teammate, by email address, or "unassigned" for tasks nobody holds. Omit for everyone.
objectiveIdstringNoOnly tasks on plans serving this objective, by the id list_objectives returns.
planIdstringNoOnly the tasks of this plan, by its id, or "none" for tasks with no plan. Omit for every plan.
searchstringNoOnly tasks whose title contains this, ignoring case.
statusone of backlog, todo, in_progress, done, canceledNoOnly tasks in this status. Omit for every status.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with plans, statusCounts, tasks, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "planId": "none"
}

Objectives and key results (list_objectives)

list_objectives. Read. Changes nothing.

The brand's objectives: the directions the team has committed to, each proven by one to four key results. Each objective carries its id, title, description, status (active, achieved, not_achieved, abandoned), target day, when it was set and closed, the live plans serving it, and its key results in order. Each key result carries its id, its exact statement, how it can be proven (metric, web or unverifiable, with the reason), its stored measure config with its scope (market, tags, query type), baseline and target, how many verdicts it holds and its newest verdict. Read it before setting or changing an objective, and before recording a verdict, which takes the key result's id and exact statement. Filter by status; page with limit and cursor.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
statusone of active, achieved, not_achieved, abandonedNoOnly objectives in this status. Omit for every status.
cursorstringNoThe nextCursor from the previous page. Omit for the first page. Opaque: pass it back unchanged.
limitintegerNoRows per page, 1 to 1000. Defaults to 100.

Returns an object with objectives, nextCursor, total.

Example call

{
  "brandId": "brand_1",
  "status": "active"
}

Add tasks (create_tasks)

create_tasks. Write. Most clients ask you before they run it. Any team member with access to the brand.

Adds up to 50 tasks to the brand's board in one call, for work a person should pick up: fixing something the data surfaced, earning a citation, updating a page. Each task needs a title; it may carry a description (the why, the evidence, what done looks like; never assignment or dates, which have their own fields), a status (todo unless given), a priority, an assignee ("me" or a teammate's email), a due day (YYYY-MM-DD), and a plan, by id (planId, from list_tasks) or by title (planTitle: a live plan with that title is reused, otherwise a new active plan is created, and sibling tasks with the same title land in one plan). The call is all or nothing: if any task cannot be added (an unknown teammate, a plan that is archived or still in review), nothing is added and the answer names the task by its position. Each new task records a "created" entry in its activity under the person the call acts as, and an assignee is notified as on the board. Send an idempotencyKey to make a retry safe. Call list_tasks first so you do not add a task the board already holds.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
taskslist of objectYesThe tasks to add, in the order they should appear.
tasks[].assigneestringNoWho the task is for: "me" for the person this call acts as, or a teammate's email address. A name alone cannot be resolved; ask for the email.
tasks[].descriptionstringNoThe task body: the why, the evidence, and what done looks like.
tasks[].dueDatestringNoThe due date, as a full UTC calendar day (YYYY-MM-DD). A month or quarter ("August 2026", "Q3") is not a date: agree a specific day with the user first.
tasks[].planIdstringNoPut the task on this existing live plan, by the id list_tasks returns.
tasks[].planTitlestringNoPut the task on the live plan with this title, or on a new active plan with it when none exists.
tasks[].priorityone of urgent, high, medium, lowNourgent, high, medium or low. Omit for none.
tasks[].statusone of backlog, todo, in_progress, done, canceledNoThe task's starting status. Defaults to todo.
tasks[].titlestringYesThe task title, imperative and specific.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "fixes-2026-11-05",
  "tasks": [
    {
      "assignee": "me",
      "description": "Three answers cite a competitor's pricing page.",
      "dueDate": "2026-11-20",
      "planTitle": "Pricing citations",
      "priority": "high",
      "title": "Publish a comparison page"
    },
    {
      "planId": "plan_1",
      "status": "backlog",
      "title": "Brief the PR agency"
    }
  ]
}

Change tasks (update_tasks)

update_tasks. Write. Most clients ask you before they run it. Any team member with access to the brand.

Changes up to 100 existing tasks in one call, each by its id from list_tasks, with a patch of only the fields to change: title, description, status, priority (null clears it), assignee ("me", a teammate's email, or null to unassign), due day (null clears it), or plan (planId, null for no plan, or planTitle, which reuses or creates a live plan). Each patch is compared with the stored task and only the fields that differ are written, so a replayed patch changes nothing and says so (changed: false). Every changed field is recorded in the task's activity under the person the call acts as, a task marked done records who completed it, a plan's status follows its tasks, and a new assignee is notified, exactly as on the board. Every task is checked before anything is written: a task this brand does not hold, one on an archived plan or a plan still in review, or a declined task refuses the whole call, naming it by position. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
updateslist of objectYesThe changes, one entry per task. A task appears once.
updates[].patchobjectYesThe fields to change. At least one must be present.
updates[].taskIdstringYesThe task to change, by the id list_tasks returns.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "idempotencyKey": "triage-2026-11-05",
  "updates": [
    {
      "patch": {
        "assignee": "sam@example.com",
        "dueDate": "2026-11-20",
        "priority": "high"
      },
      "taskId": "task_1"
    },
    {
      "patch": {
        "assignee": null,
        "planId": null
      },
      "taskId": "task_2"
    },
    {
      "patch": {
        "planTitle": "Pricing citations"
      },
      "taskId": "task_3"
    }
  ]
}

Add task comments (create_task_comments)

create_task_comments. Write. Most clients ask you before they run it. Any team member with access to the brand.

Posts up to 50 comments on the brand's tasks in one call, each on a task by its id from list_tasks, written as the person the call acts as. A comment is plain text; to mention a teammate write @Name, where id is the teammate's id as list_tasks gives it for an assignee, and that teammate is notified, as the task's assignee is, exactly as on the board. Use it to record what was found, decided or done on a task. A task this brand does not hold refuses the whole call, naming the comment by position. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
commentslist of objectYesThe comments to post, in order.
comments[].bodystringYesThe comment text.
comments[].taskIdstringYesThe task to comment on, by the id list_tasks returns.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.

Returns an object with created, notificationsDelivered, replayed.

Example call

{
  "brandId": "brand_1",
  "comments": [
    {
      "body": "The page went live today.",
      "taskId": "task_1"
    }
  ]
}

Add plans (create_plans)

create_plans. Write. Most clients ask you before they run it. Any team member with access to the brand.

Adds up to 20 plans to the brand in one call. A plan groups tasks under one title and may serve one active objective (objectiveId, from list_objectives). A new plan is active at once, with no tasks: add them with create_tasks and its planId. A title another live plan of this brand already carries is not added again: that plan is reported under alreadyExisted, so a retry converges. When the call names an objective for such a plan, a plan serving no objective is linked to it, and one serving another objective refuses the call with conflict (move it with update_plans). An objective this brand does not hold, or one that is closed, refuses the whole call. A plan's status is derived from its tasks.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
planslist of objectYesThe plans to add. Each title appears once.
plans[].objectiveIdstringNoThe active objective this plan serves, by its id.
plans[].titlestringYesThe plan's title.

Returns an object with alreadyExisted, created, replayed.

Example call

{
  "brandId": "brand_1",
  "plans": [
    {
      "objectiveId": "objective_1",
      "title": "Earn review citations"
    }
  ]
}

Change plans (update_plans)

update_plans. Write. Most clients ask you before they run it. Any team member with access to the brand.

Changes up to 50 existing plans in one call, each by its id from list_tasks: rename it, or link it to an active objective (objectiveId) or unlink it (null). Each patch is compared with the stored plan and only what differs is written, so a replayed patch changes nothing and says so. A plan's status is derived from its tasks, so it is not a field: complete a plan by moving its tasks to done with update_tasks. An archived plan, a title another live plan already carries, or a closed objective refuses the whole call, naming the plan by position.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
updateslist of objectYesThe changes, one entry per plan. A plan appears once.
updates[].patchobjectYesThe fields to change. At least one must be present.
updates[].planIdstringYesThe plan to change, by the id list_tasks returns.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "updates": [
    {
      "patch": {
        "objectiveId": null
      },
      "planId": "plan_1"
    }
  ]
}

Set objectives (create_objectives)

create_objectives. Write. Most clients ask you before they run it. Any team member with access to the brand.

Sets up to 10 objectives for the brand in one call, each a qualitative direction the team commits to, agreed with the user first, with 1 to 4 key results. Each objective is active at once. Each key result is a free-text statement classified honestly by how it can be proven: 'metric' (from this brand's tracked Heading measures; attach a measure config only then, and only for a measure Heading tracks, with the baseline and the scope taken unchanged from get_objective_measure), 'web' (provable by web search or fetch at verification time), or 'unverifiable' (state the reason; an honest unverifiable classification is better than a made-up metric). The answer carries each objective's id and its key result ids. Send an idempotencyKey to make a retry safe.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
objectiveslist of objectYesThe objectives to set.
objectives[].descriptionstringNoNarrative context: why this objective, as agreed.
objectives[].keyResultslist of objectYesThe key results that will prove it, in order.
objectives[].targetDatestringNoA target day (YYYY-MM-DD). Nothing happens on it by itself: verification is always asked for.
objectives[].titlestringYesThe objective: the direction, stated plainly.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "objectives": [
    {
      "keyResults": [
        {
          "statement": "Named in most answers about small-firm CRMs",
          "verifiability": "metric"
        },
        {
          "statement": "Listed on the top three review sites",
          "verifiability": "web"
        }
      ],
      "targetDate": "2027-03-31",
      "title": "Become the default CRM for small accounting firms"
    }
  ]
}

Change objectives (update_objectives)

update_objectives. Write. Most clients ask you before they run it. Any team member with access to the brand.

Changes up to 10 active objectives in one call, each by its id from list_objectives. A patch can change the title, the description, the target day (null clears it), and the key results as the FULL new set: an entry with an id keeps or edits that key result, an entry without one adds a key result (or keeps the stored one with the same statement), and stored key results missing from the set are removed. The set must hold 1 to 4; a key result that already carries verdicts cannot be removed; a closed objective cannot be changed. Every objective is checked before anything is written, and any refusal refuses the whole call, naming the objective by position.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
updateslist of objectYesThe changes, one entry per objective. An objective appears once.
updates[].objectiveIdstringYesThe objective to change, by the id list_objectives returns.
updates[].patchobjectYesThe fields to change. At least one must be present.

Returns an object with replayed, updated.

Example call

{
  "brandId": "brand_1",
  "updates": [
    {
      "objectiveId": "objective_1",
      "patch": {
        "targetDate": null,
        "title": "Lead small-firm CRM answers"
      }
    }
  ]
}

Record key result verdicts (create_key_result_verdicts)

create_key_result_verdicts. Write. Most clients ask you before they run it. Any team member with access to the brand.

Records up to 20 verdicts on key results in one call, during a verification the user asked for: 'met', 'not_met' or 'inconclusive', each with markdown evidence citing exactly what was checked (the measures read, the pages fetched, links included). 'inconclusive' is an honest outcome when the check cannot decide; never record 'met' on inference alone. Pass each key result's id and its statement exactly as list_objectives gives it: a verdict whose statement no longer matches the stored one, or whose objective is no longer active, refuses the whole call. Verdicts never overwrite: a new check appends a new verdict, and the statement judged is kept with it. The Objectives page shows who recorded each verdict and how.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
idempotencyKeystringNoAny unique string for this request. A retry with the same key and arguments writes nothing new and returns the first answer, for a day.
verdictslist of objectYesThe verdicts to record.
verdicts[].evidencestringYesMarkdown evidence for the judgment: what was checked and what was found, links included. Cite tool answers, never an inferred figure.
verdicts[].keyResultIdstringYesThe key result, by the id list_objectives returns.
verdicts[].statementstringYesThe key result's statement, exactly as list_objectives gives it.
verdicts[].valueone of met, not_met, inconclusiveYesmet, not_met or inconclusive.

Returns an object with created, replayed.

Example call

{
  "brandId": "brand_1",
  "verdicts": [
    {
      "evidence": "Mentioned in 64% of answers on 2026-11-04 (list_prompts).",
      "keyResultId": "kr_1",
      "statement": "Named in most answers about small-firm CRMs",
      "value": "met"
    }
  ]
}

Agent readiness

How ready the brand's own website is for AI agents. The one tool that measures the site rather than what AI says about it.

Agent readiness (get_agent_readiness)

get_agent_readiness. Read. Changes nothing.

The brand's latest Agent readiness Run: whether the brand's own public website can be found, read and used by the machines that produce AI answers. Answers the 0 to 100 score and its grade (A+ to F), the four weighted Layers (Discovery 20, Access 30, Usability 40, Payments 10) with the points each earned, the fixes ranked by the points a full pass would add, and every Probe with its outcome, the URL it fetched, what it observed and what to change. These are the figures the brand's Agent readiness page shows. Each Probe is one HTTP observation a person can repeat; none states a cause or a forecast, and nothing here joins visibility data. A brand never measured answers run null; a Run that could not reach the site answers no Probes and a null score, never a clean result; a Layer nothing applied to has null points, never zero. Use it when someone asks whether their site is ready for AI agents, or before proposing site-side work: it is the only tool that measures the brand's own site. Filter the Probes by outcome; choose the domain when the brand has more than one.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
domainstringNoWhich of the brand's domains to read, as list_brands reports them. Defaults to the brand's main domain, as the page does.
outcomeone of pass, fail, warning, error, pending, not_applicableNoOnly Probes with this outcome. fail and warning are the ones to act on. Omit for every Probe. The score, Layers and fixes are always the whole Run.

Returns an object with domain, domains, fixes, grade, gradeLabel, layers, note, probes, run, score.

Example call

{
  "brandId": "brand_1",
  "domain": "acme.com",
  "outcome": "fail"
}

Traffic

The Traffic page: Google Analytics, Search Console, leads and AI crawler activity.

Traffic report (get_traffic_report)

get_traffic_report. Read. Changes nothing.

The brand's Traffic page as data, for one UTC day range: what AI visibility led to, and which AI crawlers fetched the brand's site. Answers Google Analytics figures (AI-referred sessions, leads, revenue, conversion rate, each with its change against the equal-length range before), leads from every connected source and each source's own count, the lead webhook's state and recent leads, Google Search figures from Search Console (clicks, impressions, CTR, position, top queries and pages), a daily series of visibility beside AI sessions and search crawls, and crawl activity from the brand's own server logs: requests by category (Search, Retrieval, Training, never added into one measure), the verified share, what the site answered, each crawler, the most fetched URLs, pages people use that no search crawler fetched, and AI arrivals in the logs beside Analytics sessions. A source that is not connected answers null with a reason, never 0. Log entries and Analytics sessions are different units and are never added together; nor are Search Console clicks and sessions. Crawl activity is part of a paid plan: without one, crawl answers state plan_required and every other part still answers. With no range asked for, each part opens on the page's own default: the last 30 UTC days for Analytics, leads and Google Search, the last 28 for crawl activity. A named range covers every part. Use it when someone asks whether AI visibility is producing anything, or whether AI crawlers can reach the site.

Arguments

ArgumentTypeRequiredWhat it is
brandIdstringYesThe brand to work on, by the id list_brands returns. A brand this connection cannot reach answers brand_not_found.
endDaystringNoThe last UTC day of the range, YYYY-MM-DD. Give both startDay and endDay, or neither for the last 30 days ending today.
startDaystringNoThe first UTC day of the range, YYYY-MM-DD. At most 366 days before endDay.

Returns an object with analytics, crawl, daily, googleSearch, leads, previousRange, range, webhook.

Example call

{
  "brandId": "brand_1",
  "endDay": "2026-10-31",
  "startDay": "2026-10-01"
}

On this page

BrandsBrands (list_brands)Change brand settings (update_brand)PromptsTracked prompts (list_prompts)Add tracked prompts (create_prompts)Change tracked prompts (update_prompts)Remove tracked prompts (delete_prompts)TagsTags and tag folders (list_tags)Create tags (create_tags)Change tags (update_tags)Delete tags (delete_tags)Create tag folders (create_tag_folders)Rename or reorder tag folders (update_tag_folders)Delete tag folders (delete_tag_folders)Saved viewsSaved views (list_views)Create saved views (create_views)Change saved views (update_views)Delete saved views (delete_views)CompetitorsTracked competitors (list_competitors)Add tracked competitors (create_competitors)Change tracked competitors (update_competitors)Stop tracking competitors (delete_competitors)Decide competitor candidates (update_competitor_candidates)ReportsVisibility report (get_visibility_report)Objective measure (get_objective_measure)Perception report (get_perception_report)Sources report (get_sources_report)AnswersAI answers (list_answers)AI answer (get_answer)Measurement changesMeasurement changes (list_changes)Prompt researchPrompt candidates (list_prompt_candidates)Pull quote (get_pull_quote)Pull prompt candidates (pull_prompt_candidates)Move prompt candidates (move_prompt_candidates)Add prompt candidates (add_prompt_candidates)ContextContext documents (list_context_docs)Context document (get_context_doc)Add context documents (create_context_docs)Edit context documents (update_context_docs)Move context documents (move_context_docs)Delete context documents (delete_context_docs)Tasks, plans and objectivesTasks and plans (list_tasks)Objectives and key results (list_objectives)Add tasks (create_tasks)Change tasks (update_tasks)Add task comments (create_task_comments)Add plans (create_plans)Change plans (update_plans)Set objectives (create_objectives)Change objectives (update_objectives)Record key result verdicts (create_key_result_verdicts)Agent readinessAgent readiness (get_agent_readiness)TrafficTraffic report (get_traffic_report)