Start Agent Build Session
Open a build session — the FIRST step of building or revising an agent.
Call this BEFORE anything else when the user asks you to build (or
substantially revise) an agent — before `list_agent_definition_templates`,
before `list_agent_node_types`, and before any `create`/`update`. It is the
entry point to building: those discovery and write tools are steps that come
AFTER you have opened a session here and are carrying its id. Call it once per
build attempt. Pass `intent`: a short, plain-language description of what the
user wants the agent to do (e.g. "weekly visibility summary for the Banking
category"). You do not need every detail yet — that is what the returned
`clarifying_questions` help you pin down with the user.
Returns a freshly minted `agent_build_session_id` plus priming guidance:
- `agent_build_session_id`: an opaque id for THIS build attempt. Pass it,
UNCHANGED, on every subsequent build call (validate_agent_definition →
create_agent_definition → update_agent_definition → publish_agent_definition)
so the whole attempt is traced as one episode. Do not invent your own.
- `clarifying_questions`: the things worth confirming with the user before
building — inputs/outputs, what success looks like, which Profound data and
category, linear vs. branching.
- `good_prompt_examples`: what a buildable request looks like, to steer the
user if their ask is vague.
This call is safe and free: it persists nothing and validates nothing — it
only mints the id and returns guidance. It does NOT create an agent.
Next: gather any missing detail with the user, then browse
`list_agent_definition_templates` for a proven starting point (or
`list_agent_node_types` to assemble from scratch), and thread the returned
`agent_build_session_id` on every build call.
Validate Agent Definition
Check whether a saved draft agent definition is well-formed and publishable.
Returns `{valid, issues[], input_schema, output_schema}` for the draft
identified by `agent_id`. Each issue carries a `code`
(`missing_field`, `invalid_graph`, `invalid_input`, or
`schema_preview_failed`), a human `message`, and — when the fault can be
pinpointed — `node_id`, `node_title`, `field`, and `field_title`, enough to
fix the exact offending field yourself without asking the user.
`invalid_input` flags a field that is present but unusable — a hub-backed
node left with an empty `integration_id` reports it with
`violation="required"`, and is fixed by resolving the id with
`list_integrations` and setting it on the node.
`schema_preview_failed` flags an output-wiring error
(e.g. an end node referencing an output no upstream node produces) that the
graph-shape check alone can't catch. When the graph is publishable,
`input_schema`/`output_schema` carry the JSON Schemas the agent will expose
once live (they are null when the graph isn't previewable). This call is safe
and free: it only reads.
This is your ONLY pre-publish safety net — there is no draft test-run, so
validate carefully before publishing. You get a `agent_id` from
`create_agent_definition(preview=False)` — which already returns the same report
inline, so call this tool only when you need to RE-CHECK an existing draft
(e.g. after fixing and re-creating it).
On `valid=false`: read `issues`, call `get_agent_node_schema` for the node
type whose field is flagged, fix that field and apply it with
`update_agent_definition(preview=False)` (edits the same draft in place).
Re-validate until `valid` is true.
On `valid=true`: the draft is publishable. Next: `publish_agent_definition(preview=True)`
to preview the go-live with the user.
`agent_build_session_id`: REQUIRED. The opaque id that
`start_agent_build_session` returned for THIS build — pass it unchanged on
every build call (validate → create → update → publish) so the whole attempt
is traced as one episode. If you have not opened a session yet, call
`start_agent_build_session` first to get one; do not invent your own id.
The report also carries `diagram`: a ready-to-render ```mermaid flowchart of the
draft's graph (node labels only, no ids). Show it to the user so they can confirm
the agent's shape at this gate before publishing. It is null when the graph has no
drawable nodes.
`graph_lint` carries advisory findings the backend `valid`/`issues` don't — most
notably, for each hub-backed node (Google Search Console, WordPress, Google Docs,
Gmail), whether its integration is already connected in this org: a connected one
just needs its `integration_id` resolved with `list_integrations` and set on the
node, an unconnected one must be connected first (the finding carries the
connect-page link) or every run will fail. These never gate the call; read them to
guide the user.
List Agent Node Types
List the node types you may use to build an agent graph.
Each entry has a `node_type` (the value you put in a node's `type` field),
a `display_name`, and a one-line `description`. This is the complete set of
types available in v1 — a graph using any other type will fail validation.
Optional `kind` narrows the catalog by WHOSE CREDENTIAL the node runs on — the
one axis that decides whether the user must go connect something first:
- `third_party` — needs an account the ORG connects (Google Search Console,
WordPress, Google Docs, Gmail). These author and publish freely, but every run
fails until that integration is connected in Profound settings AND its
`integration_id` is set on the node; `get_agent_node_schema` names which
integration under `documentation.availability`.
- `platform_key` — an outside vendor Profound pays for (`exa_*`, `firecrawl_*`,
`perplexity_*`, `serpapi_*`, `dataforseo_*`, `youtube_*`, `parallel_*`). Nothing
for the user to connect and no API key to supply — they just run.
- `native` — Profound's own: the structural nodes (`llm`, `code`, `conditional`,
`iteration`, …) and the `profound_*` data nodes, which read the org's own
Profound account.
- omit `kind` for the full catalog (default; unchanged for existing callers).
The three are exhaustive and mutually exclusive, so `native` is NARROWER than "not
third-party": a platform-key vendor needs nothing connected, yet it is not
Profound's own data.
`kind` is a static classification of the node type itself — it does NOT tell you
whether *your* org has connected an integration (that is a separate, dynamic
signal; `list_integrations` is the authority). Filtering is purely a convenience
narrowing; the payload rows are identical whether or not you pass it.
Important (beta API): being listed here is necessary but does NOT by itself
guarantee a node of that type can be published. Some types are still
stabilizing, so a graph that uses them may create as a draft yet be rejected
at publish. Treat `validate_agent_definition` and the `publish_agent_definition`
result as the
authority on publishability — not this catalog — and follow their `issues` to
correct or replace any node a publish rejects.
Use this when assembling an agent so you know which building blocks exist —
but only AFTER you have opened a build session with `start_agent_build_session`
(the entry point to building); if you have not, call that first and carry the
returned `agent_build_session_id` through the build. Next: call
`get_agent_node_schema(node_type)` for the schema and worked examples of any
type you intend to use.
Get Agent Node Schema
Get the configuration schema for one node type.
Returns `{node_type, input_schema, schema_version, description?, examples?,
documentation?, docs_version?}`. The `input_schema` describes what goes in
that node's `config`. `examples` may be null (the external API does not always
ship worked examples) — rely on the `input_schema` and `description` to build
the config. Get `node_type` from `list_agent_node_types`.
When present, `documentation` carries the AUTHORITATIVE authoring shape for
nodes whose configuration the `input_schema` does not fully spell out — in
particular nested nodes like `iteration` (its `local_variables` and
`sub_graph` envelope). Prefer `documentation` over the bare `input_schema` for
those nodes; the schema alone is not enough to author them correctly.
Read `input_schema` at runtime rather than hard-coding fields — the external
API is in beta and the schema may change between calls. It is a real JSON
Schema (typed fields, `required`, enums), not a placeholder to be ignored.
For tool nodes (the `profound_*` data nodes and the third-party provider
nodes) the `input_schema` is the node's full input parameter shape (typed
`properties`, `required`, enums) relayed from upstream, and the response ALSO
carries `output_schema` (the node's output-variable names/types) and
`default_values` (the web builder's blank-form parameter defaults), taken
from the static node-contract registry (upstream models no node outputs). Use
`input_schema` for the parameter shape and `output_schema` for the outputs you
can wire downstream. `default_values` is a blank form, not a working starting
point: required literals arrive empty (`integration_id: ""`, `post_id: ""`),
an empty required field fails validation, and a blank default reads `""` even
where `input_schema` types the field as an integer — so fill every required
field from `input_schema` rather than copying the defaults. Structural nodes
carry neither `output_schema` nor `default_values`.
`documentation.availability` tells you whether a node needs a connection: the
platform-key research nodes (`exa_*`, `firecrawl_*`, …) run on Profound's own
key and need none, while the hub-backed third-party nodes (Google Search
Console, WordPress, Google Docs, Gmail) require that
integration connected in the Profound app AND their `integration_id` set on the
node — resolve it with `list_integrations`, set it, and publish here (an empty
`integration_id` fails validation and every run). For hub-backed write nodes,
`documentation.side_effect` states the run-time mutation and the
non-destructive default to author (e.g. `wordpress_create_post` →
`status: "draft"`).
Use the schema to assemble or fix the node's `config` in your graph. Next:
once the graph is assembled, call `create_agent_definition(preview=True)` to
preview the plan — there is no validate-a-candidate step; validation happens
on the saved draft after you apply.
List Agent Definition Templates
Browse proven starting points before building an agent from scratch.
First, make sure you have opened a build session: if you have not already
called `start_agent_build_session` for this build, do that BEFORE this —
it is the entry point to building — and carry the returned
`agent_build_session_id` onto every build call. Then call this when a user
wants something that resembles a common job — a visibility snapshot, a
citation breakdown, a sentiment digest, a competitor comparison, a content
brief — to find a proven starting point. Each template gives a plain-language `goal`, the
`inputs` it needs, what it `produces`, and a `skeleton`: an ordered list of
`{node_type, role, config_hints}` steps that is the workflow's proven topology.
How to use a template: match the user's intent to a template's `goal`
semantically — the user never picks one by name. NEVER show template ids, node
types, or any other internal handle to the user; speak only in plain language.
Then use the `skeleton` as your graph spine: for each step, read its `role` and
`config_hints`, call `get_agent_node_schema(node_type)`, and fill that node's
config. The template gives you the shape and what to put; the schema gives you
the exact fields. Read each step's `config_hints` and the template's `tips`/
`portability` — they flag config details to get right (e.g. binding a Profound
node's category, or terminating every conditional branch).
Each template also lists `follow_ups`: sensible next nodes to extend the
workflow after it works, each with a plain-language `reason` — offer them by
reason, never by node type.
If no template fits, build the graph yourself from `list_agent_node_types` —
templates are a shortcut, not a requirement.
Next: assemble the graph, then `create_agent_definition(preview=True)` to
preview the plan with the user.
Get Agent Definition
Read back an agent's full workflow graph so you can copy and edit it.
Returns `{agent_id, version, graph}`, where `graph` is the `{nodes, edges}`
object in the SAME canonical dialect that `create_agent_definition` and
`update_agent_definition` accept — so you can submit it back verbatim (or after
edits) without rebuilding the envelope. This is the reliable way to author a
hard-to-assemble node (e.g. `iteration`): find an agent that already has one,
read its graph, and copy that subgraph.
`version` is `"published"` (the live, org-visible version — the default) or
`"draft"` (the latest unpublished changes, visible only to its creator). Get
`agent_id` from `list_agents`.
Caveat — the read is NOT a verbatim mirror of the friendly authoring dialect:
tool-backed nodes come back in their LOWERED `tool` form (e.g. a
`profound_visibility` node reads back as `{"type": "tool", ...}`), not the
friendly v1 `node_type`. The lowered shape is still re-submittable as-is, so a
round-trip works; just don't expect the friendly node types you authored with.
Treat `graph` as opaque data, not a typed contract: the external API is in beta
and its shape may change. Next: edit the graph and call
`update_agent_definition(preview=True)` to preview the change.
Create Agent Definition
Create a draft agent definition — preview first, then apply on confirmation.
Before calling: make sure you opened a build session with
`start_agent_build_session` at the start of this build and are passing its
`agent_build_session_id` here (see the param below); if you skipped it, call
it now. Then assemble the `graph` from `list_agent_node_types` and
`get_agent_node_schema`. There is no way to validate a graph before
creating it — validation happens on the saved draft (see below). For a
hard-to-assemble node (e.g. `iteration`), the reliable shortcut is to find an
agent that already uses one with `list_agents`, read its graph with
`get_agent_definition`, and copy that subgraph instead of building it blind.
The graph envelope (REQUIRED — read this; the per-node schema does NOT
describe it). `get_agent_node_schema` returns only that node's `data` payload
(and for start/end just `{"type":"object"}`); it does NOT describe the wrapper
every node needs. A graph is `{"nodes":[...], "edges":[...]}`. EVERY node,
regardless of type, MUST carry all of: `id` (your own string handle), `type`,
`data` (an object — the node title goes in `data.title`, NEVER at the top
level), `input_variables` (array, may be empty), and `output_variables`
(array, may be empty). Per-type rules the backend enforces:
- start: `input_variables` MUST be empty; declare the workflow inputs as
`output_variables[]`, each `{"variable": {"id": <id>, "name": <str>,
"data_type": {"kind":"primitive","type":"string"}, "required": true}}`.
- end: `output_variables` MUST be empty and `input_variables` non-empty;
declare the agent outputs in `data.outputs[]` as
`{"key": <name>, "variable_id": <id>}` — the field is `key`, not `name`.
- llm: `data` MUST contain `provider`, `model`, and `model_parameters` (with
`user_prompt`), and declare exactly one output slot whose
`expected_output_id` is `"text"`. Not every `provider`/`model` string is
accepted; if validation rejects the model, fall back to the known-good pair
`provider: "openai"`, `model: "gpt-4o-mini"`.
- edges: each needs a non-empty `id` plus `source`/`target` node ids. An edge
LEAVING a conditional node selects its branch with a TOP-LEVEL `sourceHandle`
equal to that case's `case_id` (e.g. `"sourceHandle": "case_false"` for the
else branch) — NOT a `data.case_id`.
- iteration body nodes are wired by `parentId` only — there is NO edge from the
iteration node into its body (entry is implicit); chain multi-node bodies with
`type:"iteration-inner"` edges; a single-node body has no edges. An edge into a
body node ("crosses scopes") is rejected. The iteration COLLECTS its leaf body
node's output (the body node with no outgoing `iteration-inner` edge), so the
iteration's own output must mirror that leaf: its `data_type` is
`array<leaf-type>` (item_type == the leaf's data_type) and its
`expected_output_id` equals the leaf's (an LLM leaf is `"text"`, a code leaf is
`"output"`). A mismatch is rejected.
- list/array data types use `{"kind":"array","item_type":{...}}` where
`item_type` is ITSELF a full type object, never a bare type name: a list of
strings is `{"kind":"array","item_type":{"kind":"primitive","type":"string"}}`
and a list of objects uses `item_type:{"kind":"primitive","type":"json"}`.
A bare `item_type` (`{"kind":"array","item_type":"string"}`), a
`{"kind":"list"}`, and a JSON-Schema `{"items":...}` wrapper are all rejected.
- a multi-line free-text field uses `{"kind":"long-text"}` — `long-text` is
its OWN kind, NOT a primitive type; `{"kind":"primitive","type":"long-text"}`
is rejected. Single-line strings stay `{"kind":"primitive","type":"string"}`.
- variable references in any field use `{{<variable-id>}}` (that variable's id
only, never a node name) and that id must appear in the using node's
`input_variables`.
Variable ids: give each variable a plain readable id (`topic`, `article_text`) —
the server assigns real UUIDs for you on create/update. Do NOT generate UUIDs or
shell out to `uuidgen`. Declare the id in `output_variables[].variable.id`, then
reference that SAME id everywhere it is used (`{{id}}`, `input_variables[].
variable_id`, `data.outputs[].variable_id`); matching ids are rewritten to one
shared UUID. (An already-UUID id is left as-is; an id you reference but never
declare stays flagged, so it still surfaces as an error.)
`profound_*` data nodes (visibility, sentiment, citation_*) REQUIRE a
`data.category_id` (a category UUID). The graph still validates and publishes
without it, but the agent FAILS at RUN time on the first Profound node with
`category_id is required` — scope is NOT resolved at the org level at run time.
Set `data.category_id` on every Profound node. When your graph contains any
Profound node, the result carries `category_snapshot` — the org's real
`{id, name}` categories — so you can bind `data.category_id` to a literal UUID
from it WITHOUT a separate `list_categories` call. The `graph_lint`
runtime_warning flags any Profound node whose category_id is missing, and
flags any whose category_id is set but is not one of the snapshot ids (an
invented/stale UUID). The agent-builder guide resource (§2b, on the resources
surface) has the full version.
Date fields on Profound nodes are literal — never variables. Use a `date_range`
enum (default `last_7_days`); only `answer_engine_insights`/`prompt_answer` take
literal `start_date`/`end_date`. A templated date, or `date_range:"custom"`
without literal `YYYY-MM-DD` dates, is rejected at create.
Some tool nodes cap a text parameter's length only at RUN time (the node schema
does NOT advertise the limit): notably `create_content_brief`'s `citations`
(~2048 chars). Binding such a field to a large upstream output (e.g. a raw
`article_research_report`) validates and publishes fine, then FAILS at run time —
insert an `llm` node that condenses the text first and bind THAT node's output.
Minimal valid graph — copy this shape, swapping in your own node types, ids,
variable names, and prompts (start -> llm -> end). Variable ids here are plain
readable names; the server assigns UUIDs on create/update:
```json
{
"nodes": [
{
"id": "start", "type": "start", "data": {"title": "Start"},
"input_variables": [],
"output_variables": [
{"variable": {"id": "topic", "name": "topic",
"data_type": {"kind": "primitive", "type": "string"}, "required": true}}
]
},
{
"id": "summarize", "type": "llm",
"data": {
"title": "Summarize", "provider": "openai", "model": "gpt-4o-mini",
"model_parameters": {
"user_prompt": "Summarize {{topic}}"
}
},
"input_variables": [
{"variable_id": "topic", "required": true}
],
"output_variables": [
{"variable": {"id": "summary", "name": "summary",
"data_type": {"kind": "primitive", "type": "string"}, "required": true},
"expected_output_id": "text"}
]
},
{
"id": "end", "type": "end",
"data": {
"title": "End",
"outputs": [
{"key": "result", "variable_id": "summary"}
]
},
"input_variables": [
{"variable_id": "summary", "required": true}
],
"output_variables": []
}
],
"edges": [
{"id": "e1", "source": "start", "target": "summarize"},
{"id": "e2", "source": "summarize", "target": "end"}
]
}
```
Flow shape — default to a SINGLE linear path (start -> ... -> end) unless the
user asked for branching. More than one outgoing edge from a NON-conditional
node (start/llm/tool/iteration) means the branches run in PARALLEL and each
ends in its own end node — use that only when you deliberately want parallel
work. For if/else routing use a conditional node, whose cases MUST be `case_true`
(first, condition-bearing) and `case_false` (else), and give each terminal branch
its own end node (never share one end across branches). Output variable labels
must be specific, not generic (`text`/`result`/`output`/...). The result's
`graph_lint` surfaces these (legal in the backend, but mishandled by the visual
builder): unintended fan-out, a conditional case not named `case_true`/`case_false`,
an end shared across branches, a generic output label, and a multi-node iteration
body not chained with `iteration-inner` edges.
Choosing the org (required, never guessed): `organization_id` MUST be the
`id` returned by `list_organizations`. If the user has more than one
organization, ask them which one BY NAME, then pass that org's
`list_organizations` `id`. Never show or ask the user for a UUID.
Preview/apply loop:
- `preview=True` (the DEFAULT) persists NOTHING and validates nothing. It
returns a plain-language `plan` describing what will be created. Relay the
plan to the user — never show graph JSON, node types, or UUIDs — and get
explicit confirmation. The result also carries `diagram`: a ready-to-render
```mermaid flowchart of the agent, in human labels (no ids/UUIDs). Show it
verbatim so the user SEES the workflow before confirming — it is instant in
preview, unlike the apply round-trip.
- `preview=False` creates the draft, returns its `agent_id`, and includes
the saved draft's `validation` report. If `validation.valid` is false, the
draft exists but CANNOT be published: read `validation.issues`, call
`get_agent_node_schema` to fix the offending fields and call
`update_agent_definition(preview=False)` to apply the fix to the SAME draft
in place (no need to re-create per fix). Call this ONLY after the user
confirms.
Always honor the returned `hint` for the next step — and when it lists
connect URLs for hub-backed nodes, give the user each one verbatim as a full
clickable link, never shortened to a settings breadcrumb.
Name collision: if `list_agents` already shows an agent with the name the
user asked for, do NOT silently create a duplicate — ask the user whether to
pick a new name or edit the existing one via the Profound UI.
Long conversations: if this chat has grown very long (roughly past 80K
tokens), the graph you hold may have drifted — re-confirm the plan with the
user before applying, and check `validation` on the result.
Timeout/retry: if an apply call times out and you cannot tell whether the
draft was created, call `list_agents` BEFORE retrying so you don't create a
duplicate.
Next: after `preview=False` succeeds with `validation.valid=true`, call
`publish_agent_definition(preview=True)` to preview going live. There is no draft
test-run — validation is the only pre-publish check, so if `validation.valid`
is false, fix the issues with `update_agent_definition` before publishing.
`agent_build_session_id`: REQUIRED. The opaque id that
`start_agent_build_session` returned for THIS build — pass it unchanged on
every build call (validate → create → update → publish) so the whole attempt
is traced as one episode. If you have not opened a session yet, call
`start_agent_build_session` first to get one; do not invent your own id.
Update Agent Definition
Update a draft agent definition's graph — preview first, then apply on confirmation.
Use this to FIX or revise an existing draft in place instead of creating a new
one. After create_agent_definition(preview=False) (or publish) reports
validation issues, correct the graph and call this — the draft keeps the same
agent_id, no new draft is spawned.
This REPLACES the whole draft graph — send the complete corrected graph,
not a partial diff. The graph must follow the SAME envelope as
create_agent_definition (see its description, or the agent-builder guide
resource §2b on the resources surface): every node needs
id/type/data (title in data.title)/input_variables/output_variables, start/end/
llm per-type rules, edges need id, variable refs are {{variable-id}} (use plain
readable ids — the server assigns UUIDs; do NOT generate them). The same authoring
gotchas apply: literal dates on Profound nodes (never variables), iteration body
nodes wired by `parentId` only (no container->body edge; `iteration-inner` edges
between siblings, and the iteration's output must mirror its leaf body node —
`array<leaf-type>` item_type and the leaf's `expected_output_id`), conditional
cases `case_true`/`case_false`, `long-text` as its own data-type kind (not a
primitive type), run-time text caps on some tool params (e.g. `create_content_brief`
`citations` ~2048 chars — condense large upstream output first), and specific
(not generic) output labels.
Inserting a node = REWIRE, not append. To put a new node N between existing
nodes A and B, send the full graph with the A->B edge REMOVED and edges A->N
and N->B ADDED. Do NOT keep A->B and also add a new branch off A (or off
start) — that creates a second, parallel branch that runs independently and
ends in its own end node, which is almost never what an edit intends. More
than one outgoing edge from a NON-conditional node (start/llm/tool/iteration)
means PARALLEL execution; only a conditional node should branch, and only for
if/else routing. If the apply result carries `graph_lint` warnings, you
likely appended instead of rewiring — fix the edges and re-apply.
Preview/apply loop:
- preview=True (the DEFAULT) persists NOTHING and validates nothing. It returns
a plain-language plan. Relay it to the user — never show graph JSON, node
types, or UUIDs — and get explicit confirmation. The result also carries
`diagram`: a ready-to-render ```mermaid flowchart of the edited graph, in
human labels (no ids/UUIDs). Show it verbatim so the user SEES the new shape
before confirming. preview does not verify the agent_id exists; an unknown or
stale id only fails when you apply (preview=False).
- preview=False replaces the draft's graph and returns its re-validated
`validation` report inline. If validation.valid is false, the draft still
exists with the new graph but CANNOT be published: read validation.issues,
call get_agent_node_schema to fix the offending fields, and call
update_agent_definition(preview=False) again (same draft). Loop until valid.
Always honor the returned `hint` — and when it lists connect URLs for
hub-backed nodes, give the user each one verbatim as a full clickable link,
never shortened to a settings breadcrumb.
Next: once validation.valid is true, call publish_agent_definition(preview=True)
to preview going live.
`agent_build_session_id`: REQUIRED. The opaque id that
`start_agent_build_session` returned for THIS build — pass it unchanged on
every build call (validate → create → update → publish) so the whole attempt
is traced as one episode. If you have not opened a session yet, call
`start_agent_build_session` first to get one; do not invent your own id.
Publish Agent Definition
Publish a draft agent definition so the agent goes live — preview first, then apply.
This is the final step of building an agent. Publishing flips it from a
private draft to a live, runnable state.
Publishing is the authoritative STRUCTURAL validation gate. The backend
validates the graph's structure and output wiring as part of publishing,
BEFORE anything changes, so a structurally broken draft is REJECTED and
stays a draft. A rejected `preview=False` does NOT raise: it returns a
`validation` report with `valid=false` whose `issues` name the offending
node and field. The result's `new_status` stays `draft`. Run prerequisites
are NOT publish gates: a graph missing a hub-backed node's `integration_id`
or a Profound node's `category_id` publishes fine and then fails at run —
`validate_agent_definition` and the `graph_lint` advisories on create/update
flag those, so clear them BEFORE publishing.
On a rejected publish: read `validation.issues`, call
`get_agent_node_schema` for the flagged node's type, fix that field and apply
it with `update_agent_definition(preview=False)`, then publish again. Loop
until the publish succeeds.
Validate first to save a round-trip: `create_agent_definition(preview=False)` already
returns a `validation` report, and `validate_agent_definition(agent_id)` re-checks
an existing draft. But that draft-level check is LENIENT (topology only) and
can report `valid=true` for a graph publish then rejects — so the publish
result is the final word, not the draft validation.
Preview/apply loop:
- `preview=True` (the DEFAULT) changes NOTHING and does not validate. It
returns the would-be transition (`previous_status` → `new_status`) so you
can confirm with the user in plain language. Never show graph JSON, node
types, or UUIDs — tell the user what publishing means (the agent becomes
live and invocable), and get explicit confirmation.
- `preview=False` performs the publish (and applies the validation gate). Call
this only after the user confirms. If it returns `validation.valid=false`,
fix the named node/field and retry per the loop above.
Always honor the returned `hint` for the next step.
Publishing is safe to repeat: if the agent is already published, this is a
no-op transition (it does not create a duplicate or error).
Next: after `preview=False` succeeds (no `validation` failure), the agent
is live and can be invoked with `run_agent`.
`agent_build_session_id`: REQUIRED. The opaque id that
`start_agent_build_session` returned for THIS build — pass it unchanged on
every build call (validate → create → update → publish) so the whole attempt
is traced as one episode. If you have not opened a session yet, call
`start_agent_build_session` first to get one; do not invent your own id.
The result also carries `diagram`: a ready-to-render ```mermaid flowchart of the
agent's graph (node labels only, no ids). Show it to the user — on preview as a
final look before go-live, on a clean apply as a receipt of what shipped. It is
null when the graph has no drawable nodes.
List Agents
List agents defined for the authenticated organization.
Agents take structured inputs and produce structured outputs. This list does
NOT include each agent's input schema. To run one you must first call
`get_agent` on it: run inputs are keyed by the schema's opaque UUID property
keys (not the human titles), so `get_agent` → build inputs → `run_agent` is
the required sequence — the `get_agent` step cannot be skipped.
``statuses`` filters by lifecycle state — typically ["published"] for the
end-user surface, or ["draft"] for editing. Defaults to ["published"].
Get Agent
Get details of an agent, including its `input_schema`.
The `input_schema` field is a JSON Schema describing required and optional
inputs. The LLM should construct an `inputs` dict matching this schema
before calling `run_agent`.
Property keys in `input_schema.properties` are opaque UUIDs, not
human-readable names. The display name for each field lives in that
property's `title`. When building `inputs` for `run_agent`, the dict
keys must be the UUIDs (not the titles).
Run Agent
Start an agent run.
Triggers a run of the named agent. The `inputs` dict must
match the agent's `input_schema` (fetch with `get_agent` first). The
dict keys must be the UUID property keys from `input_schema.properties`,
not the human-readable `title` strings. Agent runs consume Magi compute
and will use agent credits, so only call this when the user intends to run
the agent.
Note: schema validation is performed by Magi at run time, not at
request time. An `inputs` dict that omits required fields will be
accepted here and the run will surface a terminal `failed` status
via `get_agent_run` with an `error` describing the missing field.
Returns immediately with a `run_id` and `status="queued"`. Poll
`get_agent_run` to check progress; terminal statuses are `succeeded`,
`failed`, `cancelled`, `skipped`. The result's `hint` names the next step
(how to read the run back, including seeing each step's output).
Get Agent Run
Get the status (and outputs if terminal) of a previously triggered run.
Terminal statuses: `succeeded` (outputs populated), `failed` (error
populated), `cancelled`, `skipped`. Non-terminal: `queued`, `running`.
Poll at most every ~2 seconds.
The result carries `steps`: an ordered trace of what the run did — one entry
per node that ran, with its kind, human title, status, timing, and (for a
conditional) which branch it took. It also carries `narration`: a ready-to-read
plain-language summary of that journey. Relay `narration` to the user so they
see what the run actually did, not just the final `outputs`. Set `verbose=True`
only when they want to inspect each step's raw output payload (a heavier
response); the step list and narration always ship. The result's `hint` names
the next step for you — poll again while the run is unfinished, or (on a default
read of a finished run) re-call with `verbose=True` to see each step's output.
Whoami
Return information about the authenticated principal — user ID,
granted scopes, and the Profound entitlements visible to this session. Use
to confirm who Claude is acting as before calling list_* or get_*_report
tools. To see the organizations in scope — and to get the ``org_id`` those
tools take — call ``list_organizations``.
List Categories
Tracked categories for an org (e.g. "Banking — Canada").
``org_id`` is the organization id from ``list_organizations``.
Required precursor for tools that take a ``category_id`` argument — this
tool is how the LLM discovers the categories available.
List Tags
Tags defined in a category.
Pass the returned tag IDs to ``list_prompts`` via ``tag_ids`` to filter prompts.
List Topics
Topics defined in a category.
List Domains
Domains tracked for an org. Use as the input source for the ``domain``
argument of ``get_bots_report`` / ``get_referrals_report``.
``org_id`` is the organization id from ``list_organizations``.
List Knowledge Bases
Knowledge bases accessible to the caller (resolver for search).
Returns one row per knowledge base with ``id``, ``name``, ``slug``,
``description``, and ``created_at``. The KB ``id`` is the input to
``search_knowledge_base``.
Use this when the user wants to enumerate the knowledge bases
configured for their account ("what KBs do we have?"). Do NOT use
this when the user wants to look up content INSIDE a KB — that's
``search_knowledge_base``.
If you are unsure what a Profound knowledge base is or what belongs
in one, read the ``knowledge-base`` glossary term and the knowledge
base guide resource before acting.
Search Knowledge Base
Semantic / full-text search inside a single knowledge base.
Returns ranked hits with ``id`` (document or chunk path), ``score``
(relevance), ``content`` (the matched text), and ``metadata`` —
including ``folder_path`` (the document's folder; empty string means
the KB root), ``source_filename``, and ``filetype``. Useful for
grounding an answer in the caller's curated documents, and the only
way to discover a KB's existing folder structure.
Use this when the user wants to look up CONTENT inside a known KB —
"search our docs for X", "what does our handbook say about Y". Do
NOT use this when the user wants to enumerate KBs — that's
``list_knowledge_bases``. Pass the KB's UUID directly when you
have it (e.g. from a prior ``list_knowledge_bases`` call); pass
the KB's name or slug and the tool will resolve it for you.
If you are unsure what a Profound knowledge base is or what belongs
in one, read the ``knowledge-base`` glossary term and the knowledge
base guide resource before acting.
Share Feedback
Report that Profound's MCP tools could not fully satisfy the user's request.
Call this when you hit a wall: an existing tool fell short of what the user
needed, or the user wanted something the Profound product can do but no MCP
tool exposes yet. This is how gaps get surfaced to the Profound team for
triage — so the signal you provide directly shapes what gets built next.
Before calling, work through these four questions — they map one-to-one to
the fields below, and the quality of your answers is the whole value of the
submission:
1. What did the user *originally* ask for — their real goal, not the
narrowed-down version you settled for?
2. What was actually possible with the tools available, and what was NOT?
What did you end up doing instead, if anything?
3. Is this a case of an existing tool needing to do more/better
(`capability_improvement`), or a capability that exists in Profound
with no MCP tool at all (`new_tool_parity`)?
4. How much did this gap actually matter to the user's task
(`High` / `Medium` / `Low`)?
Answer from your read of the conversation first. If the user is in the loop
and one of these is genuinely unclear — you're guessing at their real goal,
or unsure how much the gap mattered — ask them one brief clarifying question
before submitting. This is optional and best-effort: never block on it, never
require a reply, and if the client has no user to ask (headless/automated),
just infer and submit. A single high-signal submission with an inferred
detail beats stalling.
Write `intent` and `outcome` as concrete, situation-specific prose — not
boilerplate. Generic filler ("user wanted data, tool didn't have it") is
worthless for triage; name the specific data, action, or capability. Good
feedback anchors the gap to real Profound capabilities (a report, metric,
tool, or UI feature that exists); a vague product wish that ties to nothing
is not actionable.
List Integrations
List the integrations your org has connected, to fill a node's `integration_id`.
Hub-backed nodes (Google Search Console, WordPress, Google Docs, Gmail,
OpenAI Ads) require an `integration_id` — the id of a connection your org made in the
Profound web app. This tool returns those connections so you can fill the
field with a real id. Call it whenever a graph you are building uses a
hub-backed node, before you try to publish.
Each connection carries `integration_id`, `provider`, `account`, `label`,
`status`, and `level` (`org` or `user`). `account` is the connected account as
the provider reports it — a Google address, a WordPress site URL — and is the
field that tells two connections of one provider apart. `label` is usually just
the connector's name, repeated identically across every row, so never ask a user
to choose by it; fall back to it only when `account` is empty. Pass
`provider` to narrow to one family, using the exact slug the node declares:
`google_search_console`, `wordpress`, `google_docs`, `google_gmail`, or
`openai_ads`.
Because those are the hub-backed families, an empty list for one of them is a
definitive "not connected" — a normal answer, not an error and not a wrong
slug; tell the user to connect it in the Profound web app first.
Google Docs has no connection of its own — it rides the org's Google Drive
connection, so a `google_docs` node is satisfied by a row whose `provider`
reads `google_drive`. Ask for `google_docs` anyway; that row is the right one
to bind the node to. `google_sheets` resolves to that same Drive row, but
there are no Google Sheets nodes to bind it to — a row coming back for it is
the Drive connection, never evidence that a Sheets step is available, so do
not offer the user one on the strength of it.
Choosing which id to write — follow these rules exactly:
- Only ever write an `integration_id` this tool returned. NEVER invent one and
NEVER copy one from another agent — a wrong id binds the node to an account
that is not yours and fails at run time.
- If exactly one connection exists for the provider and the field is empty,
fill it and tell the user which `account` you used.
- If more than one connection exists, always ask the user to choose by
`account` — never pick for them. Binding the wrong one is quiet: the graph
publishes and the run succeeds, against somebody else's account.
- Leaving `integration_id` empty is better than a made-up placeholder: an
empty field is a clean "not chosen yet", while a truthy placeholder looks
valid and suppresses the builder's own account picker.
Returns only active connections. A connection is only usable if its `status`
is `active`; do not bind a node to a broken one.
Add Knowledge Base Document
Add ONE new document (inline markdown text) to a knowledge base.
Purely additive: this creates a new document and never overwrites one.
Revision and deletion are UI-only — when the user wants to change or
remove an existing document, direct them to the Profound platform.
Typical uses: uploading FactCheck brand source documents during
onboarding, and ingesting brand guidelines / product specs / campaign
briefs that agents read as context.
BEFORE submitting, read the knowledge base guide resource: classify
the content's intent (brand-facts, product-spec, faq, policy, or
brief), shape ``text`` with the matching markdown + YAML frontmatter
template from the guide's templates subresource, and check it clears
the guide's minimum bar. The tool accepts inline text only — if you
have a URL, fetch and read the content yourself, then submit the
resulting markdown. If you are unsure what a Profound knowledge base
is, read the ``knowledge-base`` glossary term first.
Defaults to preview=True: nothing is ingested; the response echoes the
plan (resolved KB, name, folder, text size) plus a non-blocking
``quality_advisory``. Pass preview=False to apply. A duplicate document
name is rejected upstream — the response ``hint`` then says to retry
under a versioned name (e.g. 'brand-guidelines-v2'). After a successful
apply, verify with ``search_knowledge_base`` (content may take a moment
to index). NOT idempotent: each apply creates a new document.
List Models
AI engines (ChatGPT, Gemini, Perplexity, etc.) the caller has visibility into.
The upstream endpoint does not accept an org filter.
List Organizations
List organizations available to the authenticated principal.
Wraps the Profound SDK's `organizations.list()`. Returns the
sandbox-seeded list when ``MCP_SANDBOX_ENABLED=true``.
List Projects
List a category's projects as thin triage rows (in backend order).
A project is a goal (e.g. "reach visibility rank 3 on Dental Insurance") with
a measurable target and a set of tasks. Each row is lightweight —
``title``, ``status``, ``summary``, ``topics``/``task_types``,
``new_task_count``, ``updated_at`` — enough to spot which project is relevant.
The **goal, resolved identity, and the tasks themselves are NOT on a list
row**: call ``get_project`` to step into one and see its goal and its tasks
(each with its own impact). There is no org-wide "list every Project" mode;
resolve the target category first with ``list_categories``. Use ``status`` to
narrow to ``suggested`` (proposed, not yet committed), ``tracked`` (actively
measured), or ``retired`` (archived); omit for all. Paginate with ``offset``
(the backend vends no total count).
New to Projects? Read the Profound Projects guide resource for the concepts
(a project's goal/``measurement``, the resolved ``identity`` block, task
types, and the impact rubric) in full.
Get Project
Get one project: its goal, why it matters, and its full task list.
Returns the goal (``title`` + simplified ``measurement``), the resolved
``identity`` (asset + topics + tracked field) — use it to pull live metrics via
``get_visibility_report`` / ``get_citations_report`` to see where you stand vs.
the target — and the ordered ``tasks`` **fully hydrated** (each with its
executor ``brief``); there is no separate task-fetch step. The Profound
Projects guide resource defines the concepts (``measurement``, ``identity``,
task types, impact) in full.
The result carries a readable markdown rendering of the project (goal, verify
steps, tasks) alongside the structured data.
Get Project Status
Get one project's lifecycle and live-generation status.
Project status states: ``suggested`` means generated/proposed but not yet
committed as tracked work; ``tracked`` means active work being measured
against the Project goal; ``retired`` means archived/no longer active.
``live_generation`` appears only when the backend reports a current or
recent generation event for the Project. Live generation states:
``queued`` is waiting to start, ``running`` is in progress, ``completed``
means the Project is generated, and ``failed`` means generation failed
(check ``live_generation.error`` when present). The result includes a
Project deeplink when the ids are known. When generation is ``completed``,
call ``get_project`` to inspect the generated goal and tasks.
New Project
Preview or kick off a fire-and-forget project generation run in a category.
Before calling, run a short interview: what kind of work should this produce
(``work_types``), what topic/target is involved, which region/persona/platform
matters, what context is available, and what the user wants to do with the
output. ``focus`` is required: one concrete sentence naming the Project the
user wants generated. Put longer steering detail in ``context``: why now,
audience/persona, market constraints, competitors, source-material summaries,
examples to emulate or avoid, and success criteria.
For every Projects preview/apply write, call with ``preview=True`` first,
show the returned plan/diff to the user, then stop. Call with
``preview=False`` only after a separate user reply explicitly confirms that
preview. Do not preview and apply in the same assistant turn.
``work_types`` is controlled vocabulary for the kind of work the generated
Project should produce: ``publish`` (new owned pages), ``refresh`` (improve
existing owned pages), ``earned`` (third-party inclusion/outreach),
``creative`` (campaigns, tools, research, videos, benchmarks), and ``social``
(distribution or YouTube/social roundup work). Defaults to ``preview=True``:
this returns the normalized generation plan without creating anything. Pass
``preview=False`` only after that explicit confirmation. Generation runs ~10
minutes: apply returns a ``project_id`` and Project link; poll
``get_project_status`` with ``project_id`` and ``category_id`` until it
completes or fails.
Archive Project
Preview or archive (retire) a project — set it aside without deleting it.
Non-destructive and reversible: the project and its history are preserved,
just marked ``retired``. Defaults to ``preview=True``: this returns the
archive plan without changing the project. Ask the user briefly why they're
archiving (e.g. not relevant, already done, wrong target, superseded) and
pass it as ``reason``.
Always call with ``preview=True`` first, show the returned diff to the user,
then stop. Call with ``preview=False`` only after a separate user reply
explicitly confirms that preview. Do not preview and apply in the same
assistant turn.
Create Project Task
Preview or add a task ("play") to a project.
The ``brief`` is a structured, per-``type`` object (its required keys depend
on the type). Prose must avoid raw citation counts (use ranks / relative
descriptors) and bare decimals (use ``33%``, ``#2``, ``+1pp``); violations are
rejected. Defaults to ``preview=True``: this returns the normalized task plan
without changing the project.
Always call with ``preview=True`` first, show the returned plan to the user,
then stop. Call with ``preview=False`` only after a separate user reply
explicitly confirms that preview. Do not preview and apply in the same
assistant turn.
Update Project Task
Preview or update fields on an existing task (send only what changes).
A supplied ``brief`` fully replaces the structured brief. Same prose rules as
``create_project_task`` apply. Defaults to ``preview=True``: this returns the
proposed change without mutation.
Always call with ``preview=True`` first, show the returned diff to the user,
then stop. Call with ``preview=False`` only after a separate user reply
explicitly confirms that preview. Do not preview and apply in the same
assistant turn.
Set Project Task Status
Preview or transition a task's status.
Status values are ``not_started`` / ``in_progress`` / ``done`` /
``abandoned``. Defaults to ``preview=True``: this returns the proposed
transition without mutation.
This is not a deletion/removal tool. ``abandoned`` keeps the task on the
Project as shelved work; use it only when the user asks to shelve, stop, or
mark a task abandoned. If the user asks to delete/remove a task and
``delete_project_task`` is not available, tell them deletion is out of scope
instead of using this tool as a substitute.
Always call with ``preview=True`` first, show the returned diff to the user,
then stop. Call with ``preview=False`` only after a separate user reply
explicitly confirms that preview. Do not preview and apply in the same
assistant turn.
Create Prompts
Create one or more prompts in a category.
Each prompt requires `prompt` (text), `topic`, `platforms`, and `regions`.
Topics and tags referenced by name are AUTO-CREATED if they don't exist —
the result's `topics_created` and `tags_created` count those.
Defaults to preview=True (no changes persisted; response shows what
WOULD happen). Pass preview=False to apply. Each apply call creates
NEW prompts — calling twice with the same input creates duplicates
(NOT idempotent).
Update Prompts
Update one or more existing prompts.
Each input requires `id` plus the fields to change. Collection fields
(tags, regions, platforms, personas) REPLACE the entire set when
provided — they don't merge. `None` = leave alone; `[]` = clear;
non-empty list = replace.
Preview returns a field-by-field diff (old → new for scalars,
added/removed for collections) per prompt. Idempotent: same input
applied twice yields the same state.
Defaults to preview=True. Pass preview=False to apply.
Update Prompts Status
Bulk activate, disable, or DELETE prompts.
status='deleted' permanently deletes prompts AND their historical data.
status='disabled' stops future runs but preserves history.
status='active' resumes daily runs.
There is no separate delete endpoint — deletion is achieved via this
tool with status='deleted'. Prompts already in the target status are
skipped (idempotent).
Defaults to preview=True. Pass preview=False to apply.
List Prompts
Prompts configured in a category, optionally filtered by status/tags/topics.
Status accepts ``active``, ``disabled``, or ``all`` (default). Include
filters (``tag_ids``, ``topic_ids``) are pushed to the upstream SDK when
``combine='AND'`` (default); ``exclude_tag_ids``, ``exclude_topic_ids``,
and ``combine='OR'`` are applied client-side after the fetch. Pass
``cursor`` from a previous response's ``next_cursor`` to page forward.
LIMITATIONS: Exclude filters and ``combine='OR'`` are applied
client-side after the upstream fetch; for very large categories this
can be slow.
Get Prompt Answers
Observed AI-engine responses for prompts in a category over a window.
Each row carries ``prompt_id``, ``country``, the answering model, the
response text, and the observation timestamp. Pass ``prompt_id`` to
restrict results to a single prompt. Pass ``country`` or ``region`` (name
or UUID; a list means any of those values) for country-level segmentation.
Use ``limit``/``offset`` for pagination (default 100 rows, upstream cap
50,000).
List Regions
Regions (US, UK, CA, etc.) available to the caller.
``org_id`` is optional: omit it to return regions across all
organizations the caller belongs to. When set, it is the organization id
from ``list_organizations``.
Get Visibility Report
Visibility report for a category over a date window.
The response is the v2 shape: an `info` block (query metadata) plus keyed
`data` rows. Dates are Eastern Time calendar dates and both start_date and
end_date are inclusive (start_date=2026-04-01, end_date=2026-04-30 covers
all of April 2026).
Metrics (default `visibility_score`):
- `visibility_score` — share of answers mentioning the asset.
- `share_of_voice` — proportion of all mentions.
- `average_position` — mean rank when mentioned (lower is better).
MCP returns raw decimals; multiply visibility_score by 100 for the web UI's
percentage (0.42 → 42%).
Brand scoping:
- Every row carries an `asset` (`{name, owned}`), and `asset` is ALWAYS an
implicit grouping key: results are one row per asset x group_by bucket,
not one row per bucket. Under scope="all" (or with multiple tracked
brands) expect a row per asset for each group_by value — sum by
`asset.name` yourself for a single row per bucket. Use `assets="YourBrand"`
(or a list) to target specific brands, or `scope` to choose `owned`
(your tracked brands, the default) vs `all` (includes competitors).
Grouping:
- `group_by` buckets results server-side. Supported: date, model, topic,
region, prompt, persona. Add `date` for a time series.
Filtering:
- Convenience kwargs (values matched by name):
topic_filter=["Personal banking"], tag_filter, region_filter,
model_filter=["<model_id-uuid>"] (UUID from list_models),
persona_filter=["<persona_id-uuid>"] (UUID).
- For advanced conditions pass a raw v2 `filter` tree:
filter={"field": "topic", "op": "is", "value": "Personal banking"}
filter={"and": [ {...leaf...}, {...leaf...} ]}
Convenience kwargs and `filter` are merged under `and`.
- Filter values are NEVER captured by analytics.
- A filter that matches no tracked data (e.g. a `region_filter` for a region
this category doesn't track) returns zero-valued rows, NOT an error — treat
all-zero results as "no data for that filter", not a bad request.
Pagination:
- `limit` caps how many top-level groups come back per page — upstream
default 10, max 50. It is NOT a total-row cap: every row is also split
per asset (see Brand scoping) and per additional group_by dimension, so a
grouped query returns more rows than `limit` (e.g. group_by=["topic"] with
limit=10 and two tracked assets is ~10 x 2 = 20 rows; add `date` over a week
and it fans out further).
- If `info.next_cursor` is present, pass it as `cursor` to fetch the next
page with the same query shape.
Related tools:
- `get_citations_report` — which sources/domains drive that visibility.
- `get_sentiment_report` — the tone behind the visible mentions.
Get Citations Report
Citations report for a category over a date window.
The response is the v2 shape: an `info` block (query metadata) plus keyed
`data` rows, always ranked most-cited first. Dates are Eastern Time calendar
dates and both start_date and end_date are inclusive.
Default shape — cited domains:
- A citation is a cited DOMAIN, so with NO `group_by` each row is one cited
domain: {domain, rank, count, citation_share}, ranked most-cited first
(rank 1 = most cited). "Which sites / sources / domains do AI engines cite
most" needs no group_by at all — just category_id + a date window.
Metrics (default `count`, `citation_share`):
- `count` — raw citation frequency.
- `citation_share` — share averaged per AI model (comparable across models).
- `rank` is on every row (1 = most cited); `first_cited_at` is pages-only.
Scope:
- `scope="all"` (default) covers every domain; `scope="owned"` narrows to
your owned domains.
Grouping:
- Pass `group_by` to re-bucket results server-side along a different axis
than the by-domain default. Supported: page, date, model, topic, region,
persona, prompt. Use `group_by=["page"]` for one row per cited page URL
(finer than domain); add `date` for a time series; use `topic`/`model`/etc.
to break citations down along that axis.
- IMPORTANT: `group_by=["page"]` is page-level and CANNOT be combined with
`scope="owned"` or with `domain_filter` — the backend rejects it (those
narrow to a domain, which is incompatible with per-page rows). To scope to
your domains, drop `page` from `group_by` (the default by-domain shape, or
any non-page grouping, works with `scope="owned"` / `domain_filter`).
Filtering:
- Convenience kwargs (values matched by name):
domain_filter=["acmebrands.com"] — subdomain-aware domain match
page_filter=["https://acmebrands.com/pricing"]
citation_category_filter=["owned"] — owned/competition/social/…
analysis_type_filter=["visibility"] — visibility/sentiment/factcheck/all
topic_filter, region_filter,
model_filter=["<model_id-uuid>"], persona_filter=["<persona_id-uuid>"]
- For advanced conditions pass a raw v2 `filter` tree:
filter={"field": "domain", "op": "is", "value": "acmebrands.com"}
filter={"and": [ {...leaf...}, {...leaf...} ]}
Convenience kwargs and `filter` are merged under `and`.
- Filter values are NEVER captured by analytics — only a boolean
`had_filters` flag and the field names are emitted to telemetry.
Pagination:
- `limit` caps how many top-level groups (e.g. cited pages/domains) come
back per page — upstream default 10, max 50. It is NOT a total-row cap:
each group also expands across every other group_by dimension, so
group_by=["page","date"] with limit=10 over a 7-day window can return up
to ~10 x 7 = 70 rows. Groups still come back ranked most-cited first.
- If `info.next_cursor` is present, pass it as `cursor` to fetch the next
page with the same query shape.
Related tools:
- `get_visibility_report` — how that citation footprint maps to brand visibility.
- `compare_owned_domain_citations` — roll up citation share across a set of
owned domains grouped into brand entities.
Get Visibility Report V1
Visibility report for a category over a date window.
Default metric: visibility_score. Supported metrics include
visibility_score, share_of_voice, mentions_count, executions,
average_position. Useful dimensions: date, region, topic, model,
prompt, tag, persona, asset_name.
Important visibility_score semantics:
- Units: MCP returns raw decimal values. Multiply by 100 to match
the web platform's percentage display. Example: a brand-scoped
visibility_score of 0.42 means 42% visibility in the web UI.
- Brand scoping: if you do not filter to a brand/asset or include
asset_name as a dimension, visibility_score is summed across every
tracked brand/asset in the category. These category-wide sums can
exceed 1.0 (for example, 3.57 means 357 percentage-points across
tracked assets, not 357% for one brand).
- Single-brand pattern: pass
filters=[{"field": "asset_name", "operator": "is", "value": "YourBrandName"}]
or asset_filter="YourBrandName". Add asset_name to dimensions
with topic when you want per-topic-per-brand rows:
dimensions=["topic", "asset_name"].
- Date windows are half-open: start_date is inclusive and end_date
is exclusive. To include all of April 2026, use start_date=2026-04-01
and end_date=2026-05-01; end_date=2026-04-30 excludes April 30.
Filtering (PRD-007):
- Use convenience kwargs for the most common filters:
topic_filter=["Personal banking"] — narrow to specific topics
model_filter=["<model_id-uuid>"] — single AI model (UUID from
list_models; display names
like "ChatGPT" are NOT
accepted)
persona_filter=["<persona_id-uuid>"] — single persona (UUID only;
SDK has no persona_name field)
tag_filter, region_filter, asset_filter
- For advanced filters not covered by convenience kwargs, pass the
raw `filters` list directly:
filters=[{"field": "topic_name", "operator": "is", "value": "Personal banking"}]
Convenience kwargs and `filters` are merged.
- Filter values are NEVER captured by analytics.
Top-N queries with `limit`:
- `limit=10` caps the response to 10 rows (server-side cap, not
pagination). When `limit` is set, `next_cursor` is always None —
no follow-up call needed. `truncated` reflects whether more rows
existed beyond the cap (e.g. `total_rows=1000, returned_rows=10,
truncated=true`).
Pagination:
- Default page_size is 500 (covers most interactive queries without
truncating). Maximum is 10000.
- If `truncated=true` in the response, either:
(a) Follow the `hint` and reformulate with narrower dimensions
(preferred — server-side aggregation is faster than
client-side filtering).
(b) Paginate by passing the returned `next_cursor` back in. Each
page takes ~6s (p50) - 14s (p99); stay within your client's
tool-call timeout budget.
(c) Raise page_size up to 10000 for export-scale queries.
- `page_size` only affects the first page; when `cursor` is set, the
size encoded in the cursor wins.
Version note: this is the v1 (legacy positional-row) tool. A newer
version with a richer v2 `info`/`data` response shape — recursive
`filter` trees, `group_by`, and `scope`/`assets` scoping — is available
at the bare tool name `get_visibility_report`. New integrations should
prefer `get_visibility_report`.
Get Citations Report V1
Citations report for a category over a date window.
Default metrics: count, citation_share. Useful dimensions: hostname,
path, root_domain, url, model, topic, prompt, tag, persona.
Filtering (PRD-007):
- Use convenience kwargs for the most common filters. These are
translated to structured filter dicts automatically:
root_domain_filter=["acmebrands.com"] — show only your brand's citations
hostname_filter=["blog.acmebrands.com"] — narrow to a specific hostname
model_filter=["<model_id-uuid>"] — single AI model (UUID
from list_models; display
names are NOT accepted)
persona_filter=["<persona_id-uuid>"] — single persona (UUID only;
SDK has no persona_name field)
topic_filter, tag_filter, region_filter,
citation_category_filter — other supported dimensions
- For advanced filters not covered by the convenience kwargs, pass the
raw `filters` list directly:
filters=[{"field": "root_domain", "operator": "is", "value": "acmebrands.com"}]
filters=[{"field": "root_domain", "operator": "in", "value": ["a.com", "b.com"]}]
Convenience kwargs and `filters` are merged — both can be used together.
- Filters on dimensional fields (root_domain, hostname, etc.) require
the corresponding dimension to be in `dimensions`. E.g.
`root_domain_filter=["acmebrands.com"]` must be paired with
`dimensions=["root_domain"]` — the API returns 422 otherwise.
- Filter values are NEVER captured by analytics — only a boolean
`had_filters` flag and the field names are emitted to telemetry.
Dimensions vs filters:
- `dimensions` controls server-side aggregation (grouping): pass
`dimensions=["root_domain"]` to get one row per domain across all
citations. This is the right choice for "show me all cited domains".
- `filters` / convenience kwargs scope the query to a subset of data:
`root_domain_filter=["acmebrands.com"]` returns only rows where
root_domain is acmebrands.com. This is the right choice for
"show me only my brand's citations".
Top-N queries with `limit`:
- `limit=10` caps the response to 10 rows (server-side cap, not
pagination). Use this for "show me the top 10 most-cited domains"
— it's faster than fetching a full page and slicing client-side.
- When `limit` is set, `next_cursor` is always None — no follow-up
call needed. `truncated` reflects whether more rows existed beyond
the cap (e.g. `total_rows=26000, returned_rows=10, truncated=true`
means there are 26k cited domains but you asked for the top 10).
- `limit` and `cursor`/`page_size` are independent: cursor paginates
through the full dataset; limit caps the total rows returned.
Pagination:
- Default page_size is 500 (covers most interactive queries without
truncating). Maximum is 10000.
- If `truncated=true` in the response, either:
(a) Follow the `hint` and reformulate with narrower dimensions
(preferred — server-side aggregation is faster than
client-side filtering).
(b) Paginate by passing the returned `next_cursor` back in. Each
page takes ~6s (p50) - 14s (p99); stay within your client's
tool-call timeout budget.
(c) Raise page_size up to 10000 for export-scale queries.
- `page_size` only affects the first page; when `cursor` is set, the
size encoded in the cursor wins.
Related tools:
- `get_visibility_report` — how that citation footprint maps to brand visibility.
- `compare_owned_domain_citations` — roll up citation share across owned domains.
Version note: this is the v1 (legacy positional-row) tool. A newer
version with a richer v2 `info`/`data` response shape — recursive
`filter` trees, `group_by`, and `scope` scoping — is available at the
bare tool name `get_citations_report`. New integrations should prefer
`get_citations_report`.
Get Sentiment Report
Sentiment report for one brand/asset.
The response contains an `info` block plus keyed `data` rows. Dates are
Eastern Time calendar dates and both start_date and end_date are inclusive.
Required scoping:
- `category_id` selects the Profound category.
- `asset` is the brand or competitor name to analyze. It is text, not an
asset filter tree.
Metrics are chosen automatically:
- Aggregate rows return positive_sentiment and negative_sentiment.
- Theme/claim driver rows also return occurrence, the percentage of
relevant AI responses where that sentiment theme or claim appears.
Grouping:
- Use `group_by` for server-side buckets. Supported values are date,
model, topic, region, prompt, persona, tag, theme, claim.
- At most two non-date groupings are allowed; date may be added for a
time series. Internal run and competitor groupings are intentionally
not exposed.
Filtering:
- Use `theme` and/or `claim` for exact-match drilldowns. They are combined
internally as an AND filter and occurrence is returned automatically.
- Filter values are never captured by analytics.
Sorting:
- Theme/claim driver views are sorted internally by occurrence descending.
Other views are sorted internally by positive sentiment descending.
Pagination:
- `limit` is the page size (default upstream: 10, max: 50).
- If `info.next_cursor` is present, pass it as `cursor` to fetch the next
page with the same query shape.
Get Factcheck Report
FactCheck score report for one category.
Use this for FactCheck score, trend, accurate/inaccurate count, or
breakdown questions.
Required scoping:
- `category_id` selects the Profound category.
- Dates are Eastern Time calendar dates and both `start_date` and
`end_date` are inclusive.
- There is no `asset` parameter. FactCheck is category-scoped.
Grouping:
- Empty `group_by` returns the headline score.
- Supported values are date, model, region, persona, prompt, topic, tag,
citation, and theme.
- Use `["date"]` for a daily series, one dim for a breakdown, and up to
two dims for a multi-dimensional breakdown.
- `citation` can only be grouped on its own.
Filtering:
- Exact-match shortcuts are `model`, `topic`, `region`, `persona`,
`prompt`, and `tag`; each accepts a string or list of strings.
- `topic` must be an exact topic name. If uncertain, call list_topics for
the category before filtering instead of guessing a topic label.
- `topic_negate=True` negates only the topic filter.
- Raw filter trees are intentionally not exposed.
Metrics and pagination:
- Rows always contain `accuracy`, `accurate`, and `inaccurate`.
- `limit` is the page size (default upstream: 100, max: 100).
- If `info.next_cursor` is present, pass it back as `cursor` with the same
query shape to fetch the next page.
Get Factcheck Claims
FactCheck inaccurate-claims report for one category.
Use this when the user asks which inaccurate claims appeared, why they are
inaccurate, which models repeated them, or what evidence/citation sources
explain a claim.
Required scoping:
- `category_id` selects the Profound category.
- Dates are Eastern Time calendar dates and both `start_date` and
`end_date` are inclusive.
- There is no `asset` parameter. FactCheck claims are category-scoped.
Grouping:
- Empty `group_by` returns a flat paginated list of claim clusters.
- Supported values are model, region, persona, prompt, topic, tag, and
theme.
- At most one grouping dimension is allowed.
- `date`, `citation`, and `claim` are not claim groupings.
- `group_by=["tag"]` cannot be combined with a tag filter.
Filtering:
- Exact-match shortcuts are `model`, `topic`, `region`, `persona`,
`prompt`, and `tag`; each accepts a string or list of strings.
- `topic` must be an exact topic name. If uncertain, call list_topics for
the category before filtering instead of guessing a topic label.
- `topic_negate=True` negates only the topic filter.
- Raw filter trees are intentionally not exposed.
Enrichment and pagination:
- Claim rows always contain `cluster_id`, `claim`, and `occurrence`.
- `occurrence` is the response-share percentage for the claim cluster.
- `include` may request theme, reasoning, models, evidence, and
citation_sources.
- `citation_sources` can be combined with theme, reasoning, evidence, and
models in one ungrouped call; it cannot be requested when `group_by` is
present.
- `citation_sources` is slower because it enriches each claim with cited
pages, so when you request it `limit` must be 5 or less. Values above 5
are rejected with an error, not silently clamped.
- For broad cited-page mapping, stop after the top claim clusters or about
10-15 unique high-impact cited pages unless the user asks for an
exhaustive export.
- For score, trend, or accurate/inaccurate count summaries, use
get_factcheck_report instead.
- `limit` is the page size (default upstream: 25, max: 100 without
`citation_sources`; max 5 when `include` contains `citation_sources`).
- If `info.next_cursor` is present, pass it back as `cursor` with the same
query shape to fetch the next page.
Get Bots Report
Bot-traffic report for a domain over a date window.
Default metrics: count, citations.
Choosing ``dimensions``:
- For "which AI vendor crawls us the most" questions, use
``dimensions=["bot_provider"]`` — returns ~5-10 vendor rows
(OpenAI, Anthropic, Google, Perplexity, ...) with crawls already
aggregated server-side. This is the default you almost always want.
- Use ``dimensions=["bot_name"]`` only when the user explicitly asks
for individual bots (e.g. "GPTBot vs ChatGPT-User"). Returns 30+
per-bot rows that you would otherwise have to group client-side.
- Add ``"date"`` for a time series. ``"bot_type"`` partitions by
crawler category.
``domain`` must match a value from ``list_domains`` exactly. Domains
are tracked at the hostname level — ``www.example.com`` and
``example.com`` are distinct entries. If the user gives a bare
domain and you get no rows, call ``list_domains`` and pick the
matching row (often the one with the most traffic). Pass
``organization_id`` if your user has multiple orgs and the domain
is ambiguous.
Filtering (PRD-007):
- Use convenience kwargs for the most common filters:
bot_provider_filter=["openai"] — single provider; lowercase
vendor enum (e.g. openai,
anthropic, perplexity, google).
Display-name "OpenAI" returns 422.
bot_name_filter=["GPTBot"] — individual bot
bot_type_filter=["ai_assistant"] — bot category
(one of: ai_assistant, ai_training, index, ai_agent)
- For advanced filters, pass the raw `filters` list directly.
- Filter values are NEVER captured by analytics.
Top-N queries with `limit`:
- `limit=5` caps the response to 5 rows (server-side cap, not
pagination). When `limit` is set, `next_cursor` is always None —
no follow-up call needed. `truncated` reflects whether more rows
existed beyond the cap.
Pagination:
- Default page_size is 500 (covers most interactive queries without
truncating). Maximum is 10000.
- If `truncated=true` in the response, either:
(a) Follow the `hint` and reformulate with narrower dimensions
(preferred — server-side aggregation is faster than
client-side filtering).
(b) Paginate by passing the returned `next_cursor` back in. Each
page takes ~6s (p50) - 14s (p99); stay within your client's
tool-call timeout budget.
(c) Raise page_size up to 10000 for export-scale queries.
- `page_size` only affects the first page; when `cursor` is set, the
size encoded in the cursor wins.
Get Referrals Report
Referral-traffic report for a domain over a date window.
Default metric: visits.
Choosing ``dimensions``:
- For "which AI vendor sends the most referral traffic" or
"compare crawls to visits by vendor" questions, use
``dimensions=["referral_type"]`` — returns a handful of vendor /
category rows aggregated server-side. Pair with
``get_bots_report(dimensions=["bot_provider"])`` for crawl-to-visit
comparisons; both dimensions align at the vendor level.
- Use ``dimensions=["referral_source"]`` only when the user wants
individual referrer hosts (e.g. ``chat.openai.com`` vs
``perplexity.ai``).
- Add ``"date"`` for a time series.
``domain`` must match a value from ``list_domains`` exactly. Domains
are tracked at the hostname level — ``www.example.com`` and
``example.com`` are distinct entries. If the user gives a bare
domain and you get no rows, call ``list_domains`` and pick the
matching row (often the one with the most traffic). Pass
``organization_id`` if your user has multiple orgs and the domain
is ambiguous.
Filtering (PRD-007):
- Use convenience kwargs for the most common filters:
referral_source_filter=["openai"] — referrer vendor; lowercase
vendor enum (e.g. openai,
anthropic, perplexity). For
individual hostnames like
"chat.openai.com", use
dimensions=["referral_source"]
instead — the filter takes
vendor enums, not hostnames.
referral_type_filter=["referer"] — referral category
(one of: internal, referer, utm, none)
- For advanced filters, pass the raw `filters` list directly.
- Filter values are NEVER captured by analytics.
Top-N queries with `limit`:
- `limit=5` caps the response to 5 rows (server-side cap, not
pagination). When `limit` is set, `next_cursor` is always None —
no follow-up call needed. `truncated` reflects whether more rows
existed beyond the cap.
Pagination:
- Default page_size is 500 (covers most interactive queries without
truncating). Maximum is 10000.
- If `truncated=true` in the response, either:
(a) Follow the `hint` and reformulate with narrower dimensions
(preferred — server-side aggregation is faster than
client-side filtering).
(b) Paginate by passing the returned `next_cursor` back in. Each
page takes ~6s (p50) - 14s (p99); stay within your client's
tool-call timeout budget.
(c) Raise page_size up to 10000 for export-scale queries.
- `page_size` only affects the first page; when `cursor` is set, the
size encoded in the cursor wins.
Get Shopping Brands Report
Brand visibility inside AI shopping results for a category.
How often each brand appears when an AI engine returns a shopping or
product-comparison surface — the shopping counterpart to
`get_visibility_report`, which measures visibility across all answers.
Response is the v2 shape: an `info` block (query metadata) plus keyed `data`
rows. Dates are Eastern Time calendar dates and both start_date and end_date
are inclusive.
Use this when the user asks about brand presence in shopping, product, or
buying-intent results. Use `get_shopping_products_report` when the question
is about specific products rather than the brand overall, and
`get_visibility_report` when it isn't shopping-specific at all.
Metrics (all three returned by default):
- `visibility_score` — share of shopping answers mentioning the brand.
- `average_position` — mean rank when mentioned (lower is better).
- `visibility_rank` — the brand's rank against the others (1 = best).
Scores are raw decimals; multiply visibility_score by 100 for the web UI's
percentage (0.42 -> 42%).
Brand scoping:
- Every row carries an `asset` (`{name, owned}`), and `asset` is ALWAYS an
implicit grouping key: results are one row per asset x group_by bucket,
not one row per bucket. Use `assets="YourBrand"` (or a list) to target
specific brands, or `scope` to choose `owned` (your tracked brands, the
default) vs `all` (includes competitors).
Grouping:
- `group_by` buckets results server-side. Supported: date, topic, region,
prompt. Add `date` for a time series.
Filtering:
- Convenience kwargs (values matched by name): topic_filter, region_filter,
persona_filter, prompt_filter, tag_filter. Values must name something the
category actually tracks — an unknown topic/region/prompt is rejected
upstream, not silently ignored, so resolve names with `list_topics` /
`list_regions` / `list_prompts` first if you are unsure.
- For advanced conditions pass a raw v2 `filter` tree:
filter={"field": "topic", "op": "is", "value": "Running shoes"}
filter={"and": [ {...leaf...}, {...leaf...} ]}
Convenience kwargs and `filter` are merged under `and`.
- Filter values are NEVER captured by analytics.
Pagination:
- `limit` caps how many top-level groups come back per page — upstream
default 10, max 50. It is NOT a total-row cap: rows also split per asset
and per additional group_by dimension.
- If `info.next_cursor` is present, pass it as `cursor` for the next page.
A category with no shopping executions in the window returns zero rows —
treat that as "no shopping data for this window", not an error. Check
`get_shopping_trigger_rate_report` to confirm whether shopping surfaces
triggered at all.
The response `hint` names the next step — the drill-down grain to reach for,
or, on zero rows, how to tell "no shopping surface" apart from "not present
in it". Read it before deciding what to call next.
Related tools:
- `get_shopping_products_report` — which individual products show up.
- `get_shopping_merchants_report` — which retailers carry those results.
- `get_shopping_trigger_rate_report` — how often shopping triggers at all.
Get Shopping Products Report
Individual product visibility inside AI shopping results for a category.
One row per product (plus any `group_by` bucket), carrying how visible that
product is, where it ranks, and its rating. Response is the v2 shape: an
`info` block plus keyed `data` rows. Dates are Eastern Time calendar dates,
both ends inclusive.
Use this when the user asks which products surface in AI shopping answers,
or how one product compares to its competitors. Use
`get_shopping_brands_report` when the question is about the brand overall
rather than specific SKUs.
Metrics (ALL of the following are returned by default — pass `metrics` only
to narrow):
- `visibility_score` — share of shopping answers featuring the product.
- `average_position` — mean rank when featured (lower is better).
- `visibility_rank` — the product's rank against the others (1 = best).
- `position1_percentage` / `position2_percentage` / `position3_percentage` /
`position_above3_percentage` — how often it lands in each slot, which is
how you tell "always shown, always fourth" from "sometimes shown first".
- `product_rating` / `product_num_reviews` — the rating and review count AI
engines are seeing for the product.
Product focus:
- `target_product="Acme Trail Runner"` switches to the item view: that
product plus its closest competitors, which is the shape to use for "how
does my product stack up?".
- `include_merchants=True` inlines each product's offers (merchant name,
price, url, image). Off by default because it multiplies response size;
turn it on when the user asks where a product is sold or at what price.
Grouping:
- `group_by` supported: date, topic, prompt. Add `date` for a time series.
Note there is no `region` grouping here (unlike the brands report).
Filtering:
- Convenience kwargs: topic_filter, region_filter, persona_filter,
prompt_filter, tag_filter, plus two this report alone supports —
brand_filter (one brand's products) and merchant_filter (the products one
retailer surfaces). Values must name something the category tracks; an
unknown value is rejected upstream rather than ignored.
- For advanced conditions pass a raw v2 `filter` tree; convenience kwargs
and `filter` merge under `and`. Filter values are NEVER captured by
analytics.
Pagination:
- `limit` caps top-level groups per page — upstream default 10, max 50, and
NOT a total-row cap. Follow `info.next_cursor` via `cursor`.
The response `hint` names the next step, including which of the two extras
above is worth adding for the question at hand.
Related tools:
- `get_shopping_brands_report` — brand-level rollup of the same surface.
- `get_shopping_merchants_report` — which retailers carry these products.
Get Shopping Merchants Report
Which retailers AI engines surface for a category's shopping results.
Response is the v2 shape: an `info` block plus keyed `data` rows. Dates are
Eastern Time calendar dates, both ends inclusive.
Pick the `view` to match the question — each returns a different row grain
and its own metrics:
- `"distribution"` (default) — one row per merchant: that merchant's share
of all shopping results. Answers "which retailers dominate this category?"
Set `by_date=True` to get it as a time series (this view only).
- `"brand_share"` — one row per merchant x brand: how each brand's presence
splits within a merchant. Answers "whose products does this retailer
show?"
- `"top_products"` — one row per merchant x product: the products each
merchant surfaces most. Answers "what does this retailer actually list?"
`metrics` defaults per view, and a metric must be valid for the view you
chose — distribution returns the merchant_share/merchant_visibility family,
brand_share returns `brand_share` (plus `merchant_share` and
`visibility_rank`), top_products returns `product_visibility`/`product_rank`
(plus `merchant_visibility`). Leave it unset unless you need to narrow; an
invalid pairing is rejected upstream. `info.view` echoes the resolved view so
you can confirm which shape you got.
Filtering:
- Convenience kwargs: topic_filter, region_filter, persona_filter,
prompt_filter, tag_filter. There is deliberately NO merchant/product
filter — this report does not support filtering on either, so narrow by
topic/region/persona/prompt/tag and read the merchant rows that come back.
Values must name something the category tracks; an unknown value is
rejected upstream rather than ignored.
- For advanced conditions pass a raw v2 `filter` tree; convenience kwargs
and `filter` merge under `and`. Filter values are NEVER captured by
analytics. (Filtering by `brand` is possible via the raw tree, but ONLY in
the distribution view — the other two views reject it.)
Pagination:
- `limit` caps top-level groups per page — upstream default 10, max 50, and
NOT a total-row cap: the brand_share and top_products views expand each
merchant across every brand/product. Follow `info.next_cursor` via
`cursor`.
The response `hint` is keyed on the view that actually came back and names
the natural next cut from there.
Related tools:
- `get_shopping_products_report` with `include_merchants=True` — the inverse
cut: per-product offers rather than per-merchant rows.
- `get_shopping_brands_report` — brand visibility independent of retailer.
Get Shopping Trigger Rate Report
How often prompts make an AI engine return a shopping surface at all.
The denominator behind the other three shopping reports: of all the runs in
this category, what share produced a shopping or product-comparison result.
Response is the v2 shape: an `info` block plus keyed `data` rows. Dates are
Eastern Time calendar dates, both ends inclusive.
Use this to establish whether a category has a shopping surface worth
optimizing, to find which topics or prompts trigger shopping most, or to
explain why another shopping report came back empty or thin.
Metrics (all three returned by default):
- `total_runs` — every run in the window.
- `shopping_triggered_runs` — the subset that returned a shopping surface.
- `trigger_rate_percentage` — the ratio of the two. DESPITE THE NAME this is
a decimal fraction between 0 and 1, not a 0-100 percentage: 0.17 means
17%. Multiply by 100 to display it, exactly like `visibility_score`. It
equals shopping_triggered_runs / total_runs, so you can always verify it.
Grouping:
- `group_by` supported: date, topic, region, persona, prompt. Group by
`prompt` or `topic` for the per-prompt / per-topic trigger rate — the cut
that tells you where shopping intent actually lives. Add `date` for a
time series.
Filtering:
- Convenience kwargs: topic_filter, region_filter, persona_filter,
prompt_filter, tag_filter. Values must name something the category tracks;
an unknown value is rejected upstream rather than ignored.
- For advanced conditions pass a raw v2 `filter` tree;
convenience kwargs and `filter` merge under `and`. Filter values are NEVER
captured by analytics.
Pagination:
- `limit` caps top-level groups per page — upstream default 10, max 50, and
NOT a total-row cap. Follow `info.next_cursor` via `cursor`.
The response `hint` restates the rate at display scale, flags a thin sample
or a low rate (little surface to optimize), and names the drill-down worth
making — read it before drawing a conclusion.
Related tools:
- `get_shopping_brands_report` — brand visibility within the triggered runs.
- `get_shopping_products_report` — the products those runs surfaced.
List Resources
List all available resources and resource templates.
Returns JSON with resource metadata. Static resources have a
'uri' field, while templates have a 'uri_template' field with
placeholders like {name}.
Read Resource
Read a resource by its URI.
For static resources, provide the exact URI. For templated
resources, provide the URI with template parameters filled in.
Returns the resource content as a string. Binary content is
base64-encoded.