Limelit MCP · public reference

# Give your agent the signal. *Keep your team in control.*

Connect a supported MCP client to your property's visibility, perception, source, content, search, and crawler evidence. Limelit scopes every call to the property and role attached to your personal access token.

Start setup[Explore workflows](https://limelit.co/use-cases)

**limelit / mcp**

$ endpoint

`https://api.limelit.co/mcp`

$ transport

`Streamable HTTP · JSON-RPC 2.0`

$ authorization

`Bearer <LIMELIT_API_KEY>`

 role-aware catalog ready

**4**client families

**185**role-aware tools

**7**guided templates

01 · Authenticate once

## From account to first tool call.

You need a Limelit account, an active property membership, and a personal API key. The key is shown once; the server stores only its hash.

1. 1 **Open API keys** Sign in, choose the intended property, then open Settings → API keys. [Create key in Settings](https://limelit.co/signup?redirect_to=/settings)
2. 2 **Create and store the key** Name the key for its client and device. Copy the plaintext value when it appears.
3. 3 **Configure one client family** Use the endpoint and Authorization header exactly as shown below.
4. 4 **Verify before acting** List tools and prompts, confirm the active property, then approve write calls deliberately.

PAT authentication

### The key carries the boundary.

Header

`Authorization: Bearer <LIMELIT_API_KEY>`

Tenant

Derived from the key; never chosen by a request header.

Effective role

The lower of the role at key creation and the user's current role.

Revocation

Revoked keys are rejected on later requests.

**Keep credentials out of source control.**

Prefer masked inputs, environment variables, or an OS secret store. Never put a key in a URL, browser bundle, screenshot, issue, or shared config file.

Four client families

## Choose the surface where your team already works.

Every example targets the same HTTPS endpoint and contains a placeholder, never a live credential. Client vendors can change their settings UI; the linked references are the current source of truth for client-specific behavior.

### Claude Desktop / Code

Use the local bridge for Claude Desktop, or connect Claude Code directly over Streamable HTTP.

1. Create a Limelit API key and keep the one-time plaintext value local.
2. Choose the Desktop config or Claude Code command below.
3. Restart Desktop or run /mcp in Claude Code, then confirm the limelit server is connected.

**Claude Desktop**claude_desktop_config.json · Node.js 18+

```
{
  "mcpServers": {
    "limelit": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@0.1.38",
        "https://api.limelit.co/mcp",
        "--header",
        "Authorization:${LIMELIT_AUTH_HEADER}"
      ],
      "env": {
        "LIMELIT_AUTH_HEADER": "Bearer <LIMELIT_API_KEY>"
      }
    }
  }
}
```

**Claude Code**Terminal · user scope

```
claude mcp add --scope user --transport http limelit \
  https://api.limelit.co/mcp \
  --header "Authorization: Bearer <LIMELIT_API_KEY>"
```

[Official client reference](https://code.claude.com/docs/en/mcp)

### Cursor

Connect the remote server from a project or global MCP configuration.

1. Export LIMELIT_API_KEY in the environment used to launch Cursor.
2. Save the configuration globally or in the current project.
3. Open Settings → MCP, enable Limelit, and review tool approvals before running writes.

**Cursor**~/.cursor/mcp.json or .cursor/mcp.json

```
{
  "mcpServers": {
    "limelit": {
      "url": "https://api.limelit.co/mcp",
      "headers": {
        "Authorization": "Bearer ${env:LIMELIT_API_KEY}"
      }
    }
  }
}
```

[Official client reference](https://cursor.com/docs/mcp)

### VS Code

Use a masked input variable so the key is not written into the MCP file.

1. Run MCP: Open User Configuration, or create a workspace .vscode/mcp.json.
2. Paste the configuration and save it.
3. Start Limelit from MCP: Show Installed Servers and enter the key at the masked prompt.

**VS Code**.vscode/mcp.json or user mcp.json

```
{
  "servers": {
    "limelit": {
      "type": "http",
      "url": "https://api.limelit.co/mcp",
      "headers": {
        "Authorization": "Bearer ${input:limelitApiKey}"
      }
    }
  },
  "inputs": [
    {
      "id": "limelitApiKey",
      "type": "promptString",
      "description": "Limelit API key",
      "password": true
    }
  ]
}
```

[Official client reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration)

### Windsurf

Connect Cascade to the remote endpoint with environment-variable interpolation.

1. Export LIMELIT_API_KEY in the environment used to launch Windsurf.
2. Add the remote HTTP configuration to mcp_config.json.
3. Refresh Cascade's MCP list, enable only the tools you need, and verify the server state.

**Windsurf**~/.codeium/windsurf/mcp_config.json

```
{
  "mcpServers": {
    "limelit": {
      "serverUrl": "https://api.limelit.co/mcp",
      "headers": {
        "Authorization": "Bearer ${env:LIMELIT_API_KEY}"
      }
    }
  }
}
```

[Official client reference](https://docs.devin.ai/desktop/cascade/mcp)

02 · Productized prompts

## Seven repeatable briefs with explicit evidence plans.

Discover them with `prompts/list` and render one with `prompts/get`. Each prompt embeds the active property context, validates declared arguments, and returns an ordered tool-call plan. It does not run those tools automatically.

MCP prompt

### Weekly Pulse

`limelit_weekly_pulse`

A period-over-period operating brief covering KPIs, competitors, sources, freshness, and next actions.

**Arguments**

- `window_days: optional, 1–45; defaults to 7`

MCP prompt

### Competitor Radar

`limelit_competitor_radar`

A thresholded review of tracked-competitor movement and evidence-supported drivers.

**Arguments**

- `window_days: optional, 1–45; defaults to 7`
- `threshold_pp: optional, 0–100; defaults to 10`

MCP prompt

### Engine Scorecard

`limelit_engine_scorecard`

A per-engine comparison of configured coverage, freshness, evidence, and explicit data gaps.

**Arguments**

- `window_days: optional, 1–90; defaults to 30`

MCP prompt

### Topic Heatmap

`limelit_topic_heatmap`

A topic-by-engine coverage map that ranks the most actionable measured blind spots.

**Arguments**

- `window_days: optional, 1–90; defaults to 30`

MCP prompt

### Prompt Grader

`limelit_prompt_grader`

An A-to-F audit of prompt balance, hygiene, funnel, competitor, and engine coverage.

**Arguments**

- `window_days: optional, 1–90; defaults to 30`

MCP prompt

### Source Authority

`limelit_source_authority`

Ranks the publishers AI assistants cite in your space, locates your own domain among them, and surfaces the highest-authority sources to earn.

**Arguments**

- `window_days: optional, 1–90; defaults to 30`

MCP prompt

### Campaign Tracker

`limelit_campaign_tracker`

A bounded before-and-after brief that separates measured movement from unsupported causal claims.

**Arguments**

- `campaign_date: required, YYYY-MM-DD UTC`
- `window_days: optional, 1–45; defaults to 14`
- `campaign_urls: optional, up to 10 comma-separated absolute HTTP(S) URLs`

**Client naming note**

Clients surface MCP prompts differently. In Claude Code, for example, a connected prompt can appear as a server-qualified slash command. Use your client's prompt picker or request the template by its exact catalog name.

03 · Complete tool catalog

## 185 tools, filtered to the caller's role.

The catalog below is generated from the server's own tool registry, so it lists every registered tool with the arguments that tool declares. `tools/list` returns only tools allowed by the key's effective role: owner includes admin and member; admin includes member; member is the baseline. A tool whose backing service is not configured on a given deployment is not offered there.

member+admin+owner

****Property context**Confirm the active property, then read its headline outcomes, scoreboard, topic metrics, and recent evaluation activity.*7***

- `get_active_property`member+ Return the active property (a.k.a. organization / brand): its name, website domain, slug, and the caller's role. Lets the agent confirm which brand it's reasoning about. No arguments.
- `get_kpi_history`member+ Get the DAILY SERIES behind the headline KPIs, so a change can be located in time instead of asserted. Use before claiming something moved: the series shows whether a number drifted, stepped on one day, or is simply noisy.
  - `comparison`stringoptionaloptional baseline for the delta: "none", "prev_period" or "prev_year" (default "prev_period")
  - `days`integer, 1 to 366optionaltrailing window in days (1..366, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
- `get_overview_kpis`member+ Return brand-visibility %, citation-share %, competitor + tracked-prompt counts, and the trailing-window competitor rankings + top-cited sources for the active property. Use this whenever the user asks 'how am I doing' / 'what's my visibility' / 'who's beating me'.
  - `days`integer, 1 to 90optionaltrailing window in days (1..90)
- `get_run_activity`member+ Snapshot of in-flight + recently-completed prompt runs across the four LLM providers (ChatGPT, Claude, Perplexity, Gemini). Use when the user asks 'is it still running' or 'what's the status of my last evaluation'. No arguments.
- `get_scoreboard`member+ Get the dashboard scoreboard: the headline standing of your brand against tracked competitors as the product renders it on the home dashboard. Use when the user asks how they are doing overall and expects the same numbers they see on screen. No arguments.
- `get_topic_metrics`member+ Get visibility broken down BY TOPIC rather than by prompt or engine: which subject areas the brand is strong or absent in over the window. Use for content-gap questions, where the useful unit is the topic and not the individual prompt.
  - `days`integer, 1 to 366optionaltrailing window in days (1..366, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
- `get_weekly_digest`member+ Get the product's own weekly digest: the deterministic at-a-glance summary of what changed for this organization, the same content the emailed digest carries. Distinct from gsc_weekly_digest, which is the Google Search Console digest. No arguments.

****Perception and matrix**Read measured perception: visibility, Share of Voice, sentiment, brand shape, rankings, and the prompt-by-engine matrix.*8***

- `get_brand_shape`member+ Get the brand-shape board: for each brand (yours and tracked competitors) and each attribute, the score AI engines attribute to it with the evidence snippets behind it. Scores below the minimum sample size are returned null with their n, never as zero, so an unmeasured attribute is never reported as a bad one.
  - `engine`stringoptionaloptional single engine; omit for all enabled engines
  - `window`stringoptionalevidence window; defaults to 30d
- `get_matrix`member+ Return the prompt-by-engine matrix for one explicit visibility, share-of-voice, sentiment, or position metric, over ALL measured runs. Exact twin of GET /rest/v1/prompts/matrix?metric=, including its lack of a window: neither takes one. For the same metric restricted to a window or a Segment, use get_ranking_matrix.
  - `metric`stringoptionalmatrix value to compute
- `get_perception_summary`member+ Return visibility, share of voice, sentiment, average position, and prior-window deltas for the active property.
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `platform`stringoptionaloptional AI engine
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
- `get_ranking_matrix`member+ Return the brand-by-engine ranking matrix for one metric, restricted to a window and an optional Segment. Distinct from get_matrix, which is the prompt-by-engine matrix over ALL runs with no window: use this one whenever the question has a date range or a Segment in it, and note the response shapes differ.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365); omit for all measured runs
  - `metric`stringoptionalmatrix value to compute
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
- `get_ranking_overview`member+ Get the ranking overview: where the brand places against tracked competitors across the measured prompt set, for an explicit window and optional Segment. Pair with get_matrix for the per-prompt, per-engine breakdown.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365); omit for all measured runs
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
- `get_sentiment_series`member+ Return chronological own-brand or competitor sentiment buckets for the active property.
  - `competitor_id`string (uuid)optionaloptional tracked competitor UUID
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `granularity`stringoptionalcalendar bucket; defaults to day
  - `platform`stringoptionaloptional AI engine
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
- `list_brand_attributes`member+ List grouped AI-attributed brand traits with stance, engine evidence, and last-seen time.
  - `brand`stringoptionalbrand scope; defaults to all
  - `competitor_id`string (uuid)optionalrequired only when brand is competitor
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
- `list_misinformation`member+ List newest own-brand negative or likely-hallucinated claims and correction-draft linkage. No arguments.

****Public perception reports**Create an expiring, tokenized public perception report.*1***

- `create_perception_audit`admin+ Freeze the active property's previous 30 days of perception evidence into a shareable 90-day audit URL. No arguments.

****Agent analytics**Connect known AI-bot retrieval activity to the pages answer engines cite, and ingest crawler evidence from the edge.*5***

- `get_agent_visits`member+ Return crawler visit totals, active bots, failure rate, and chronological activity buckets.
  - `bot_id`string (uuid)optionaloptional exact crawler bot UUID
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `granularity`stringoptionalactivity bucket; defaults to hour
  - `intent`stringoptionaloptional crawler intent
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `vendor`stringoptionaloptional crawler vendor, matched case-insensitively
- `get_crawl_citation_join`member+ Return the canonical path join between AI-crawler visits and owned answer citations.
  - `bot_id`string (uuid)optionaloptional exact crawler bot UUID
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `intent`stringoptionaloptional crawler intent
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `vendor`stringoptionaloptional crawler vendor, matched case-insensitively
- `ingest_agent_log_batch`member+ Send a batch of server request-log entries to one of your webhook agent-log sources, so Crawler analytics can count AI-crawler traffic. Bot identification is server-side: send every request you have and the response reports how many were stored (accepted) and how many were discarded as non-bot (dropped_non_bot). At most 500 entries per call. Pass delivery_id to make a retry safe: the same id with the same payload is an idempotent replay, while the same id with a different payload is rejected. Use one writer per source, since an external sender posting to the same source shares this delivery-id namespace.
  - `delivery_id`stringoptionaloptional idempotency key: 1 to 128 letters, digits, periods, underscores, colons, or hyphens
  - `entries`object[]optionalrequest-log entries; send everything, the server decides what is a bot
  - `source_id`stringoptionalUUID of an existing webhook agent-log source owned by your organization
- `list_bots`member+ List AI crawler bots active for the selected tenant window and filters.
  - `bot_id`string (uuid)optionaloptional exact crawler bot UUID
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `intent`stringoptionaloptional crawler intent
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `vendor`stringoptionaloptional crawler vendor, matched case-insensitively
- `list_crawl_errors`member+ List canonical paths with HTTP error responses, highest error count first.
  - `bot_id`string (uuid)optionaloptional exact crawler bot UUID
  - `from`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `intent`stringoptionaloptional crawler intent
  - `to`string (date-time)optionaloptional strict RFC3339 UTC timestamp
  - `vendor`stringoptionaloptional crawler vendor, matched case-insensitively

****Prompt research**Maintain the buyer-question set and run evaluations deliberately.*6***

- `create_prompt`member+ Add a new tracked prompt. Use when the user says 'start tracking X' or 'add a prompt for Y'. Subject to the per-org cap (default 200).
  - `category`stringoptionaloptional category (max 60 chars)
  - `location_country`stringoptionalISO 3166 alpha-2 code (e.g. 'US') or empty
  - `text`stringoptionalthe prompt text (max 500 chars)
- `delete_prompt`member+ Permanently delete a tracked prompt. Past run history is preserved.
  - `id`stringoptionalprompt UUID
- `list_prompts`member+ List the prompts being tracked for the active property. Each row carries id, text, category, location, platform_filter, and is_active.
  - `include_inactive`booleanoptionalinclude archived prompts
- `reevaluate_all_prompts`owner+ OWNER ONLY. Accept a request to evaluate every active prompt across all configured LLM platforms. This spends from the org's daily/monthly budget; the budget guard will short-circuit the run if it would exceed the cap. Returns accepted planned work while child queue insertion continues in the background; it does not report runs as enqueued. Per-prompt re-evaluation (which is cheaper) is available at member tier via `reevaluate_prompt`. No arguments.
- `reevaluate_prompt`member+ Trigger an on-demand evaluation of a single prompt across all configured LLM platforms. Bypasses the 6-hour cooldown that gates the automatic cron. Returns the number of runs enqueued.
  - `prompt_id`stringoptionalprompt UUID
- `update_prompt`member+ Patch a tracked prompt. Use to rename, recategorize, change country, or toggle active.
  - `category`stringoptionalnew category
  - `id`stringoptionalprompt UUID
  - `is_active`booleanoptionalset false to archive
  - `location_country`stringoptionalnew ISO 3166 alpha-2 code
  - `text`stringoptionalnew prompt text

****Competitors**Curate the comparison set, including the suggestion queue and its dismissals.*7***

- `dismiss_suggested_competitor`member+ Permanently dismiss a suggested brand so it stops surfacing in suggestions. Use when the brand is not actually a competitor.
  - `brand_name`stringoptionaldisplay name of the brand to dismiss
- `list_competitors`member+ List all currently tracked competitors for the active property. No arguments.
- `list_dismissed_suggested_competitors`member+ List brand suggestions that have been dismissed. Use before a restore to confirm the exact name. No arguments.
- `list_suggested_competitors`member+ List brand mentions that the LLM platforms have surfaced and we suspect are competitors but you haven't yet tracked or dismissed. Use to seed competitor-tracking decisions. No arguments.
- `restore_dismissed_suggested_competitor`member+ Undo a previous dismiss so the brand can re-surface in suggestions on the next pass.
  - `brand_name`stringoptionaldisplay name of the brand to restore
- `track_competitor`member+ Add a brand to the tracked competitors list. Domain is REQUIRED: it's the sole identity key (name-only tracking is not allowed, since two rows for the same competitor with different casing/spacing would otherwise collide with the citation-based domain tracking). Provide the display name, the canonical website domain (e.g. 'acme.com'), and an optional free-form category label.
  - `category`stringoptionalfree-form category label
  - `domain`stringoptionalwebsite domain (no scheme): required
  - `name`stringoptionaldisplay name
- `untrack_competitor`member+ Stop tracking a competitor. Past mention history is retained.
  - `id`stringoptionalcompetitor UUID

****Articles and cited sources**Inspect the articles and source domains appearing in measured answers.*3***

- `get_article`member+ Get the per-prompt × per-platform breakdown for a specific article URL: useful before drafting a competing post.
  - `url`stringoptionalthe article URL
- `list_articles`member+ List articles citing the active brand or its competitors, with lifecycle (surfaced / drafting / published), per-platform counts, and helped_competitors (which competitors are also cited on the same URL). Grain is one row per article URL, selected by whether it cites a TRACKED BRAND, so it is not a way to ask what a given domain gets cited for: use list_source_urls for that. No arguments.
- `list_top_sources`member+ List the top-cited DOMAINS (publisher / platform / blog hosts) feeding LLM answers about the active brand, ranked by citation count. Grain is one row per domain and the window is ALL TIME, with no date filter and no Segment scope. To see which individual URLs on one of these domains get cited, or to scope by window or Segment, use list_source_urls; for the ranked gap worklist use list_source_gaps.
  - `limit`integer, 1 to 200optionalmax rows (default 50, capped at 200)
  - `offset`integeroptionalpagination offset

****Source Hub**Read one cited domain in depth: its overview, mentions, prompts, URLs, and how it compares with your brand.*5***

- `compare_source_to_brand`member+ Compare one TRACKED COMPETITOR's source presence against your own over the same prompt set: retrieved counts, cited counts, and presence ratios side by side. Returns comparable=false for your own domain or an untracked source. Window defaults to the trailing 30 days; pass days to widen it.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `value`stringoptionalbare domain or host, exactly as it appears in your sources list (for example otterly.ai or blog.otterly.ai). Not a URL.
- `get_source_overview`member+ Get the identity and headline numbers for ONE source domain or host: what kind of source it is, how many times it was retrieved and cited in the window, whether your brand appears alongside it, and the run coverage the numbers rest on. Start here before the per-URL or per-prompt breakdowns. Window defaults to the trailing 30 days; pass days to widen it.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `value`stringoptionalbare domain or host, exactly as it appears in your sources list (for example otterly.ai or blog.otterly.ai). Not a URL.
- `list_source_mentions`member+ List the individual answers that mentioned the brand behind one source, with engine, prompt, and rank position. Only meaningful when the source maps to your own brand or a tracked competitor; for an unmapped source it returns resolvable=false, which is an answer and not an error. Window defaults to the trailing 30 days; pass days to widen it.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `value`stringoptionalbare domain or host, exactly as it appears in your sources list (for example otterly.ai or blog.otterly.ai). Not a URL.
- `list_source_prompts`member+ List the tracked prompts whose runs surfaced one source, with per-prompt retrieved and cited counts, the engines involved, and whether YOUR brand was present in the same answer. Use to find the specific prompts a competitor's or publisher's content is winning. Window defaults to the trailing 30 days; pass days to widen it.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `value`stringoptionalbare domain or host, exactly as it appears in your sources list (for example otterly.ai or blog.otterly.ai). Not a URL.
- `list_source_urls`member+ List the individual URLs ON one source domain that AI engines retrieved or cited, ranked, with per-URL retrieved and cited counts. This is the tool for 'what is their top cited content', 'which of their pages are winning', or 'what should I write against'. Grain is one row per URL, unlike list_top_sources which is one row per domain. Window defaults to the trailing 30 days; pass days to widen it.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `limit`integer, 1 to 200optionalmax URLs to return (default 50, capped at 200)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `value`stringoptionalbare domain or host, exactly as it appears in your sources list (for example otterly.ai or blog.otterly.ai). Not a URL.

****Source gaps and movers**Find domains that cite competitors but not you, and the ones whose citation share is moving.*2***

- `list_source_gaps`member+ List the sources feeding AI answers in your category RANKED BY GAP: how often each was retrieved or cited versus how often your brand appeared alongside it. This is the worklist behind the Sources page. Choose the grain with dimension: domains (publisher level), hosts (subdomain level), or urls (individual pages). Window defaults to the trailing 30 days.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `dimension`stringoptionalrow grain; defaults to domains
  - `limit`integer, 1 to 1000optionalmax rows (default 200, capped at 1000)
  - `min_gap`integeroptionaloptional floor on the gap score; omit for no floor
  - `q`stringoptionaloptional case-insensitive substring match on the source value
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `sort`stringoptionalordering; defaults to gap
  - `source_type`stringoptionaloptional facet
- `list_source_movers`member+ List sources whose presence CHANGED between the requested window and the window immediately before it, bucketed as top, new, trending, or losing. Both windows get their own run denominator, so a change here is a change in the sources and not in how many runs you executed. Use when the user asks what changed, what is new, or what you are losing.
  - `bucket`stringoptionalwhich movement bucket; defaults to top
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30); the prior window is the same length
  - `dimension`stringoptionalrow grain; note the SINGULAR spelling here
  - `limit`integer, 1 to 1000optionalmax rows (default 50, capped at 1000)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run
  - `source_type`stringoptionaloptional facet

****Query fan-outs**Read the follow-on queries an engine expanded a tracked prompt into.*3***

- `get_fanout_phrases`member+ Return recurring bigrams and trigrams from persisted fanout evidence for the active property. Counts are captured-event phrase occurrences, not LLM-generated suggestions. The read scans the most recent 5000 matching evidence events; total_queries reports how many were scanned and truncated is true when older events in the window were left out, so describe the result as the most recent evidence rather than the whole window.
  - `engine`stringoptionaloptional engine slug, for example gemini or chatgpt
  - `from`stringoptionaloptional RFC3339 or YYYY-MM-DD inclusive UTC lower bound
  - `limit`integer, 1 to 5000optionalmaximum aggregated fanout rows (1..5000); ignored by phrase aggregation
  - `q`stringoptionaloptional normalized-query substring
  - `to`stringoptionaloptional RFC3339 or YYYY-MM-DD exclusive UTC upper bound
  - `topic`stringoptionaloptional exact prompt topic/category
  - `type`stringoptionaloptional evidence methodology
- `get_fanouts`member+ Return captured web-search fanouts for the active property, aggregated by normalized query and evidence type. Explicit rows are provider-reported searches; inferred rows are deterministic candidate queries.
  - `engine`stringoptionaloptional engine slug, for example gemini or chatgpt
  - `from`stringoptionaloptional RFC3339 or YYYY-MM-DD inclusive UTC lower bound
  - `limit`integer, 1 to 5000optionalmaximum aggregated fanout rows (1..5000); ignored by phrase aggregation
  - `q`stringoptionaloptional normalized-query substring
  - `to`stringoptionaloptional RFC3339 or YYYY-MM-DD exclusive UTC upper bound
  - `topic`stringoptionaloptional exact prompt topic/category
  - `type`stringoptionaloptional evidence methodology
- `get_prompt_coverage_queries`member+ Return the ranked sub-questions AI engines actually searched while answering one tracked prompt, strongest first. These are provider-reported searches only, never our own reconstruction, so they describe observed retrieval behaviour rather than a guess at intent. Use them to spec an article against what the engine really looked for. Unlike the coverage read behind draft generation, this one is anchored on the PROMPT, so it answers for a query you are losing and have no page of your own to point at.
  - `prompt_id`string (uuid)optionaltracked prompt id, from list_prompts

****Google search results**Run a live Google search and read what ranks, what the AI Overview and AI Mode answer, and who they cite. These calls buy data from a search vendor, so they are charged to your credit balance.*3***

- `get_ai_mode_answer`member+ Return Google's AI Mode answer for a query: the answer text and the publisher sources behind it. AI Mode is the conversational search surface and cites differently from the AI Overview, so compare the two before concluding a brand is absent. present=false means Google showed no AI Mode answer. This makes a paid vendor call charged to the workspace credit balance.
  - `country`stringoptionaloptional ISO-3166 alpha-2 country code, for example US or DE; defaults to US
  - `language`stringoptionaloptional Google language code, for example en or de; defaults from the country
  - `query`stringoptionalthe search query to run on Google
- `get_ai_overview`member+ Return Google's AI Overview for a query: the answer text and the sources it cited. present=false means Google showed no AI Overview for that query, which is a real finding rather than an error. Use this to see whether a buyer question triggers an AI answer and who gets cited in it. This makes a paid vendor call charged to the workspace credit balance.
  - `country`stringoptionaloptional ISO-3166 alpha-2 country code, for example US or DE; defaults to US
  - `language`stringoptionaloptional Google language code, for example en or de; defaults from the country
  - `query`stringoptionalthe search query to run on Google
- `search_serp`member+ Run one live Google search and return the ranked organic results, the answer box, the People Also Ask questions, the related searches, and the AI Overview text when Google rendered it inline. Use this for what actually ranks for a query, what questions searchers ask around it, and which pages own the answer. This makes a paid vendor call charged to the workspace credit balance, so search once per distinct question rather than per phrasing.
  - `country`stringoptionaloptional ISO-3166 alpha-2 country code, for example US or DE; defaults to US
  - `language`stringoptionaloptional Google language code, for example en or de; defaults from the country
  - `query`stringoptionalthe search query to run on Google

****Keyword demand**Find the keywords a market searches for and price one exact phrase. These calls buy data from a keyword vendor, so they are charged to your credit balance.*2***

- `get_search_volume`member+ Return the monthly Google search volume for one exact phrase. has_data=false means DataForSEO has no volume for that phrase, which is common for long conversational questions and is not the same as a volume of zero, so never report it as zero demand. This makes a paid vendor call per phrase, charged to the workspace credit balance, so ask about a phrase once.
  - `location_code`integeroptionaloptional DataForSEO location code; defaults to 2840 (United States)
  - `phrase`stringoptionalthe exact phrase to price
- `list_keyword_ideas`member+ Return keyword ideas with monthly search volume, intent and CPC, either for a domain (what that site's market already searches for) or for a seed phrase (what shares intent with it). Pass target, seed, or both: with both, the domain is asked first and the seed answers only if the domain has no corpus, which is common for newer sites. Always read the returned source field before describing the result, because domain ideas and seed ideas answer different questions. This makes a paid vendor call charged to the workspace credit balance.
  - `language_code`stringoptionaloptional DataForSEO language code, for example en; defaults to en
  - `limit`integer, 1 to 100optionalmaximum keyword ideas to return (1..100)
  - `location_code`integeroptionaloptional DataForSEO location code; defaults to 2840 (United States)
  - `seed`stringoptionaloptional seed phrase such as ai visibility tracking
  - `target`stringoptionaloptional domain such as example.com; scheme and www are stripped

****Answer transcripts**Read and compare the recorded engine answers behind a measurement.*4***

- `compare_prompt_chats`member+ Compare how every engine answered ONE tracked prompt side by side: who each engine mentioned, in what order, and which sources each one cited. Use when the user asks why one engine shows them and another does not.
  - `prompt_id`string (uuid)optionaltracked prompt id
- `get_chat`member+ Get ONE engine answer in full: the answer body, every brand mention with its rank position, every cited source, and the inline attribution spans when the engine provided grounding. The body is capped; when it is cut the response sets body_truncated true and reports the original length, so never describe a truncated answer as complete.
  - `id`string (uuid)optionalchat (run) id from list_chats
- `get_chat_facets`member+ List the values actually present in your answer data for the window: which engines ran, which brands were mentioned, which source hosts were cited, which topics and features appeared. Call this before filtering list_chats so you filter on a value that exists rather than guessing a spelling.
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
- `list_chats`member+ List the individual AI engine answers behind your visibility numbers, newest first, with a one-line preview, the engine, the brands mentioned and their rank positions, and the sources cited. Returns provenance (run_count, engines, low_n) so you can say how much data a claim rests on. Use this to QUOTE evidence rather than assert a trend. Bodies are NOT returned here; call get_chat for one full answer.
  - `brand`stringoptionaloptional: only answers mentioning this brand name
  - `days`integer, 1 to 365optionaltrailing window in days (1..365, default 30)
  - `limit`integer, 1 to 100optionalmax answers (default 25, capped at 100)
  - `model`stringoptionaloptional engine filter
  - `offset`integer, 0 or moreoptionalpagination offset
  - `prompt_id`string (uuid)optionaloptional: only answers for one tracked prompt
  - `segment`stringoptionaloptional Segment id or slug; omit to include every run
  - `source`stringoptionaloptional: only answers citing this exact host, for example otterly.ai

****Opportunity plan**Read the ranked, evidence-backed opportunity queue.*2***

- `get_opportunity`member+ Get one Plan opportunity by id, with its full evidence references: which prompts and engines cited the source, which competitors it helps, and the current work state. Use after list_opportunities when the user drills into a specific row.
  - `id`string (uuid)optionalopportunity id from list_opportunities
- `list_opportunities`member+ List the Plan queue: ranked opportunities to win or defend a citation, each with its source URL, bucket (win / defend / winning), 0-100 score, proposed action, assignee, status, and the citation evidence behind it. This is the product's action list, so use it when the user asks what to do next, what to work on, or what is assigned to them. Ranking is reach multiplied by weakness, so a high score means a contested source where the brand is absent or behind.
  - `bucket`stringoptionaloptional client-side filter on the returned queue
  - `limit`integer, 1 to 200optionalmax rows to return (default 50, capped at 200)
  - `segment`stringoptionaloptional Segment id or slug; omit to score every run

****Search Console**Read connected Google Search Console evidence and the derived findings.*5***

- `gsc_findings`member+ Run all six derived GSC detectors in one call: content_decay, cannibalisation, ctr_gap, quick_wins, content_gaps, traffic_drops. Each one is empty when there's nothing to flag. Use when the user asks 'what should I work on next?' or 'where's the easy SEO win?'. No arguments.
- `gsc_property_summary`member+ Summarise the active org's Google Search Console connection. Returns the property URI, last-sync timestamp, indexed/discovered/blocked URL counts, and any sync error. Use this first when the user asks 'how is my SEO doing' so subsequent tool calls have context. No arguments.
- `gsc_search_analytics`member+ Aggregate clicks/impressions/position/CTR over the last N days for one or more URLs. Pass empty url_list to get the property-wide rollup (all tracked URLs). Use when the user asks for traffic numbers, CTR comparisons, or 'which posts are working'.
  - `days`integeroptionalLookback window. Default 7. Max 90.
  - `url_list`string[]optionalURLs to filter to. Empty = all tracked URLs on the property.
- `gsc_url_status`member+ Look up the latest GSC URL Inspection result for a specific URL on the connected property. Returns verdict, coverage state, robots/indexing state, last crawled time, and a deep-link to open this URL inside Google Search Console's web UI. Use when the user asks 'is X indexed?' or 'why isn't X showing up on Google?'.
  - `url`stringoptionalthe absolute URL to inspect (must be on the connected property)
- `gsc_weekly_digest`member+ Return the weekly Google Search Console digest: top 5 quick wins, content gaps, traffic drops, and CTR gaps, plus a one-line headline. The same content the dashboard surfaces in the digest banner. Use when the user asks 'what's new this week' or 'send me my SEO digest'. No arguments.

****Segments**Create and read saved tenant Segments with their exact-country evidence contract.*5***

- `create_segment`member+ Create a saved tenant Segment using the same validation and evidence contract as REST.
  - `definition`objectoptional
  - `kind`stringoptional
  - `name`stringoptionalhuman-readable Segment name
- `delete_segment`member+ Soft-delete one tenant-owned Segment so it no longer scopes reads.
  - `segment_id`string (uuid)optionaltenant-owned segment uuid
- `get_segment`member+ Return one tenant-owned Segment with current membership, exact-country evidence state, and unlocated-run disclosure.
  - `segment_id`string (uuid)optionaltenant-owned segment uuid
- `get_segment_members`member+ Return bounded members, exact country execution evidence, requested-country exclusions, and unlocated-run state for one segment.
  - `segment_id`string (uuid)optionaltenant-owned segment uuid
- `list_segments`member+ List saved segments with member counts, exact-country evidence state, and separately excluded unlocated legacy runs. No arguments.

****Watchlists and source graph**Track a set of sources, evaluate it on demand, and read how sources relate.*4***

- `create_watchlist`member+ Create a validated relationship watchlist attributed to the current user.
  - `definition`objectoptionaloptional relationship selector
  - `name`stringoptionalhuman-readable watchlist name
- `evaluate_watchlist`member+ Evaluate one tenant watchlist and persist its immutable evidence snapshot.
  - `watchlist_id`string (uuid)optionaltenant-owned watchlist uuid
- `get_source_relationships`member+ Return the prompt, answer, competitor, and action relationships around one source.
  - `source`stringoptionalsource domain, host, or URL shown by Sources
- `list_watchlists`member+ List active tenant watchlists using the same collection envelope as REST. No arguments.

****FactCheck evidence**Read flagged claims, the checks that produced them, and their verifications.*8***

- `get_factcheck_check`member+ Return one tenant-owned Cross Check and its exact claim, verdict, rationale, Knowledge Source evidence, citation relationships, and reviewer override.
  - `check_id`string (uuid)optionaltenant-owned Cross Check uuid
- `get_factcheck_claim`member+ Return one tenant-owned claim using the stable REST read model and its immutable evidence identifiers.
  - `claim_id`string (uuid)optionaltenant-owned FactCheck claim uuid
- `get_factcheck_result`member+ Read one recorded Cross Check as the automation scoreboard run_factcheck answers with: check id, effective verdict counts, accuracy rate, and per-claim rows with any reviewer override. Omit check_id to list the tenant's recent checks instead. For the full evidence read model behind a check, use get_factcheck_check.
  - `check_id`string (uuid)optionaltenant-owned Cross Check uuid; omit to list recent checks
- `get_factcheck_verification`member+ Return one durable Cross Check attempt with exact diagnostics, evidence fingerprint state, result check ID, and preserved last-good check ID.
  - `verification_id`string (uuid)optionaltenant-owned asynchronous verification uuid
- `list_factcheck_checks`member+ List immutable Cross Check invocations for the active tenant with exact answer-run and claim-count provenance. No arguments.
- `list_factcheck_claims`member+ List persisted FactCheck claims for one answer run without recomputing or upgrading unsupported and not-relevant verdicts.
  - `answer_run_id`string (uuid)optionaltenant-owned answer run uuid
- `list_factcheck_verifications`member+ List asynchronous Cross Check attempts for one answer, preserving queued/running/failed/succeeded and current/stale/source-deleted/empty evidence states.
  - `answer_run_id`string (uuid)optionaltenant-owned answer run uuid
- `run_factcheck`member+ Run Cross Check on one saved answer run: extract its factual claims and verify each against the tenant's active Knowledge Sources, recording an immutable check. Returns the check id, effective verdict counts, the accuracy rate (null when nothing scored), and one row per claim with any reviewer override. The verifier is deterministic and calls no model; repeating a call with the same idempotency_key returns the already-recorded check instead of running again.
  - `answer_run_id`string (uuid)optionaltenant-owned answer run uuid
  - `idempotency_key`stringoptionaloptional stable 8-128 character retry identity; a repeat with the same key returns the already-recorded check, and omitting it records a fresh check per call
  - `include_claims`booleanoptionalset false to answer with the scoreboard alone, which is what a Tables cell binds

****FactCheck plan actions**Turn exact FactCheck evidence into a pending Plan draft, then decide it. Nothing publishes or sends.*3***

- `create_factcheck_plan_action`owner+ Create an owner-attributed pending Plan draft from exact FactCheck evidence; this never approves, publishes, sends, or performs remote writes.
  - `action_kind`stringoptionalbounded internal planning handoff
  - `claim_id`string (uuid)optionaltenant-owned FactCheck claim uuid
  - `idempotency_key`stringoptionalstable 8-128 character retry identity for one logical mutation
  - `verification_id`string (uuid)optionalimmutable verification uuid belonging to the claim
- `decide_factcheck_plan_action`owner+ Record an explicit owner approve or discard decision for a pending FactCheck draft; approval only queues internal Plan work and never publishes or sends.
  - `action_id`string (uuid)optionaltenant-owned FactCheck Plan action uuid
  - `decision`stringoptionalowner review outcome
  - `idempotency_key`stringoptionalstable 8-128 character retry identity for one logical mutation
- `get_factcheck_plan_action`member+ Return one tenant-owned approval draft and the exact immutable FactCheck evidence rendered by its Plan review URL.
  - `action_id`string (uuid)optionaltenant-owned FactCheck Plan action uuid

****Owned pages**Read the pages you own and their measured evidence.*2***

- `get_owned_page`member+ Read one already-materialized tenant-owned page without identity reconciliation; includes related prompts, competing sources, answers, and action readiness.
  - `canonical_url`stringoptionalcanonical or observed owned-page URL
  - `days`integeroptionalevidence window in days; omitted defaults to 30
- `list_owned_pages`member+ List tenant-owned canonical pages after idempotently reconciling tenant-local identity from stored evidence; returns stable IDs and truthful evidence states with no remote side effects.
  - `days`integeroptionalevidence window in days; omitted defaults to 30

****Owned-page plan actions**Propose and decide an owned-page change as a pending Plan draft, never a direct write.*3***

- `create_owned_page_plan_action`owner+ Create an owner-attributed pending Plan draft from current Owned Pages evidence; this never approves, publishes, sends, or performs remote writes.
  - `action_kind`stringoptionalbounded internal planning handoff; omitted defaults to improve_page
  - `canonical_url`stringoptionaltenant-owned page URL from get_owned_page
  - `competing_url`stringoptionalobserved competing URL; required only for create_competing_page
  - `days`integeroptionalevidence window in days; omitted defaults to 30
  - `idempotency_key`stringoptionalstable 8-128 character retry identity for one logical mutation
- `decide_owned_page_plan_action`owner+ Record an explicit owner approve or discard decision for a pending Owned Pages draft; approval only queues internal Plan work and never publishes or sends.
  - `action_id`string (uuid)optionaltenant-owned Owned Pages Plan action uuid
  - `decision`stringoptionalowner review outcome
  - `idempotency_key`stringoptionalstable 8-128 character retry identity for one logical mutation
- `get_owned_page_plan_action`member+ Return one tenant-owned Owned Pages approval draft with its exact page snapshot and immutable evidence references.
  - `action_id`string (uuid)optionaltenant-owned Owned Pages Plan action uuid

****Knowledge sources**Read the knowledge sources behind a grounded answer, with their versions and refreshes.*3***

- `list_knowledge_source_refreshes`member+ List durable refresh attempts for one Knowledge Source, including persisted failure diagnostics and result version IDs without claiming failed evidence is current.
  - `source_id`string (uuid)optionaltenant-owned Knowledge Source uuid
- `list_knowledge_source_versions`member+ List immutable versions for one tenant-owned Knowledge Source, preserving exact version, content digest, and collection identifiers.
  - `source_id`string (uuid)optionaltenant-owned Knowledge Source uuid
- `list_knowledge_sources`member+ List tenant-owned Knowledge Sources with last-good version IDs, refresh health, and truthful queued/running/succeeded/failed/stale lifecycle state. No arguments.

****Editorial**Draft, review, update, publish, or remove owned content through the existing workflow.*7***

- `delete_draft`member+ Permanently delete a draft. Already-published posts remain live at their public URL.
  - `id`stringoptionaldraft UUID
- `generate_draft`member+ Generate a brand-new blog draft from an article URL. The article is fetched, summarized, and used as a prompt for the writer LLM. Returns the persisted draft. If you have an existing citation_id (from list_articles → get_article), pass it so the draft is linked to the surfaced lifecycle.
  - `source_citation_id`stringoptionaloptional citation UUID linking back to a surfaced article
  - `source_url`stringoptionalthe article URL to use as the seed
- `get_draft`member+ Fetch the full draft (title, current_md, generated_md, source_url, status, slugs) for editing or publish review.
  - `id`stringoptionaldraft UUID
- `list_drafts`member+ List blog drafts for the active property, including status (draft / published) and the latest title. No arguments.
- `publish_draft`member+ Publish a draft. Allocates a slug, freezes current_md → published_md, and exposes the post at /blog/<property-slug>/<post-slug>. The post is sitemap-listed and OG-tagged for AI crawlers.
  - `id`stringoptionaldraft UUID
- `score_content_quality`member+ Score a blog draft against the Panda content-quality rubric, the same 0-100 score and the same seven checks the editor's sidebar shows a writer: word count (25), heading structure (15), section depth (15), content variety (15), external citations (10), title quality (10), readability (10). Pass draft_id to score a saved draft, or pass title and markdown to score text you are still composing. Below 80 is advisory, not a block. Each failing check comes back with a ready-made rewrite instruction and the exact passage it applies to. This costs nothing to run and calls no model, so it is safe to call repeatedly while iterating on a draft.
  - `draft_id`stringoptionaldraft UUID to score; omit when passing title and markdown
  - `markdown`stringoptionalmarkdown body, scored by the other six checks
  - `title`stringoptionalarticle title, scored by the title-quality check
- `update_draft`member+ Update a draft's title and / or current_md (the in-progress markdown).
  - `current_md`stringoptionalnew in-progress markdown body
  - `id`stringoptionaldraft UUID
  - `title`stringoptionalnew title

****CMS integration**Hand a reviewed draft to a connected CMS as a draft.*1***

- `create_cms_draft`owner+ Create an awaiting-approval CMS draft; this tool cannot publish content.
  - `content`objectoptionalproposed structured draft content
  - `integration_id`string (uuid)optionaloptional tenant-owned CMS integration uuid
  - `target`stringoptionalCMS path, slug, or destination identifier

****GTM operations**Move reviewed content into GTM operations and queue Operate Inbox tasks. Limelit drafts outreach and never sends it.*6***

- `gtm_check_draft_quality`member+ Ask the quality gate whether a draft may be published, BEFORE publishing it. Returns publishable=true with the score when the draft clears the bar. A draft that fails comes back as a tool error carrying the severity-tagged critique, so the loop revises and re-checks instead of publishing. This is the only gate that can refuse: the verdict on gtm_publish_draft is recorded after the post is already live.
  - `draft_id`stringoptionaldraft UUID returned by gtm_create_draft
- `gtm_create_draft`member+ Start a GTM blog draft. Wraps generate_draft but takes structured GTM inputs: target_query (the GSC query we're trying to capture), validated depth + brand_strength enums, and an optional target_platform (which AI engine to bias the writing toward). Returns the draft id, current status, and the canonical URL the post will land at after publish.
  - `brand_strength`stringoptionalhow aggressively to pitch the brand. light / balanced / strong.
  - `depth`stringoptionalhow much source text to feed the writer LLM. quick=1.8k chars (cheapest), standard=6k chars (default), deep=12k chars (max grounding).
  - `source_url`stringoptionalcompetitor / reference URL whose page we're improving on
  - `target_platform`stringoptionaloptional: bias the draft toward one engine. chatgpt / perplexity / claude / gemini / ai_overview / ai_mode.
  - `target_query`stringoptionalthe buyer-intent query this post is meant to capture (e.g. 'csv to chatgpt'). The agent should include this exact phrase in the post's title + meta + first paragraph.
- `gtm_list_drafts`member+ List GTM drafts filtered by status (any of: generating, draft, published). Returns up to 200 newest-first, scoped to the active org. The loop uses this to (a) avoid queuing duplicate generations, (b) find recently-published posts to score.
  - `status`stringoptionalfilter: generating | draft | published. Empty returns all statuses (same as list_drafts).
- `gtm_publish_draft`member+ Publish a GTM draft. Wraps publish_draft and additionally returns the canonical public URL the post is now served at, so the loop can pass it to gtm_score_post for the 14-day backpressure read.
  - `draft_id`stringoptionaldraft UUID returned by gtm_create_draft
- `gtm_queue_task`member+ Enqueue an Operate Inbox task (cold_email, listicle_pitch, reply_to_thread, social_post, partnership_dm, journalist_pitch, draft_review). Returns the task id; the customer triages it in the Limelit /operate page. The Ralph loop never sends: it only enqueues.
  - `payload`stringoptionalJSON-encoded payload (shape varies per type)
  - `suggested_action`stringoptionalone-sentence agent explanation surfaced on the Inbox card
  - `type`stringoptionaltask type: see description
- `gtm_score_post`member+ Read the 14-day backpressure score for a published URL: clicks, impressions, position, CTR: pulled from the org's GSC data. The Ralph loop calls this 14 days after publish to decide whether the last move worked. Returns {connected:false} if GSC isn't connected for this org (graceful no-op).
  - `days`integeroptionallookback window in days; default 14, max 90.
  - `url`stringoptionalthe absolute published URL to score (e.g. https://chatcache.limelit.co/...)

****Composed workflows**Opinionated summaries that combine several existing evidence sources, plus the AI readiness scan.*9***

- `analyze_my_visibility`member+ Composite digest answering 'how am I doing across LLM platforms'. Returns brand-visibility %, citation-share %, top 5 competitors by mentions, and the top 5 cited sources for the trailing window. Use this as a starting point when an agent wants to reason about the property's overall posture.
  - `days`integer, 1 to 90optionaltrailing window in days (1..90)
- `find_easy_wins`member+ List the articles where the active brand is NOT yet cited but where ≥ 1 tracked competitor IS. These are pages with proven LLM-discoverability: the highest-leverage targets for outreach or counter-content. Returns up to 20 results sorted by helped_competitors count, then citation_count. No arguments.
- `get_ai_readiness`member+ Return the most recent AI Readiness Scan of the org's own website: how well the site is structured for AI crawlers and answer engines. Includes the 0-100 weighted score, letter grade, per-category scores, and every individual check with its status (pass, warn, fail), what was observed, and how to fix it. Also returns fix_prompt, a ready-to-act instruction assembled from the failing checks, intended for an agent that has access to the website's repository. status reflects the scan lifecycle: when it is 'pending' or 'running' the checks are not populated yet and the caller should call again shortly. A 'fail' is blocking and a 'warn' is advisory; treating them the same wastes effort on low-value work. Checks read the initial HTML response only and do not execute JavaScript, so markup injected client-side is reported as missing. No arguments.
- `get_country_coverage`member+ Return provider-attested AI visibility for every country represented by active geo-targeted prompts. Each row includes the prompt basis, exact region-attested completed-answer basis, excluded unverified runs, evidence state/explanation, brand visibility percentage, and leading tracked competitor when evidence exists. Countries without targeted prompts are intentionally omitted. brand_visibility_pct is null when completed_run_count is 0; scope_state=blocked means completed answers existed but no provider evidence proved execution in that country. Treat fewer than 5 attested completed answers as directional.
  - `days`integer, 1 to 90optionaltrailing window in days (1..90)
- `get_crawler_policy`member+ Return how a site's robots.txt governs every known AI crawler and the major search engines. Fetches only /robots.txt over https and evaluates it per RFC 9309: this is a deterministic parse, not a model reading a file, so the same robots.txt always yields the same verdict. Each bot returns status allowed, blocked, partial or unknown, plus the User-agent group that governed it. 'partial' means this path is allowed but the governing group restricts other paths. 'unknown' means the policy could not be read (network error or 5xx) and must never be read as permission. The bot 'type' distinguishes consequences that look alike: blocking a training bot is a licensing choice, blocking a search bot removes the site from that engine's answer index, and blocking a userQuery bot breaks live retrieval when someone pastes a URL into an assistant. Omit domain to check the organization's own configured website.
  - `domain`stringoptionalhostname to check, e.g. example.com. Defaults to the organization's configured website domain. Competitor domains are permitted.
- `get_llms_txt`member+ Return a site's llms.txt: the curated index a publisher writes for AI assistants. Checks /llms.txt and falls back to /.well-known/llms.txt, fetches it over https as PLAIN TEXT, and returns the text verbatim. It is never parsed as HTML, which matters because a plain-text file put through an HTML pipeline comes back empty or mangled. found=false with served_as_html=true is the common failure where a site answers the path with its app shell (HTTP 200 and an HTML page) instead of a 404, which means the file does not actually exist. Omit domain to check the organization's own configured website.
  - `domain`stringoptionalhostname to check, e.g. example.com. Defaults to the organization's configured website domain.
- `run_ai_readiness_scan`member+ Start a fresh AI Readiness Scan of the organization's configured website domain. Use this to confirm that changes made to the site actually landed, by scanning again after the fixes are deployed. The scan always targets the domain configured in Settings and cannot be pointed at an arbitrary host. It runs asynchronously and consumes no credits: call get_ai_readiness afterwards to read the result, which is not available immediately. Scanning a site that has not been redeployed since the last scan will return the same findings, so only re-scan after the changes are live. No arguments.
- `suggest_my_next_blog_post`member+ Pick the single highest-leverage article URL to write a counter-post against. Looks at easy-wins (where competitors are cited but the brand is not), filters to articles already in surfaced lifecycle (no draft yet), and returns one suggestion with rationale. Use as the seed input to generate_draft. No arguments.
- `summarize_recent_activity`member+ One-page digest of in-flight + recently-completed prompt evaluations across the four LLM platforms, with a per-platform breakdown so the agent can answer 'is anything still running' or 'why is the dashboard stale'. No arguments.

****Agent Library**Define, inspect, run, and schedule the grounded agents in the workspace Library.*7***

- `create_agent`member+ Register a Library agent over an already-published composed block: title, intent grouping (monitor / investigate / act), and an optional grounding policy. The block must belong to the active workspace.
  - `composed_block_id`string (uuid)optionalpublished composed block the agent wraps
  - `grounding_policy`objectoptionaloptional grounding policy payload
  - `intent`stringoptionalLibrary grouping along the verb spine
  - `is_template`booleanoptionalregister as a reusable template
  - `title`stringoptionalagent title shown in the Library
- `define_block`member+ Define a composed block: a typed-IO node graph persisted as a draft for the active workspace, validated as an acyclic DAG before it is stored. Each node is {id, kind, inputs:[{name,type}], output:{name,type}, config?, after?:[node ids]}; a kind of "mcp:<tool_name>" executes that registered MCP tool. Graph outputs with type draft, action, correction, outreach, or page carry a write posture: running them produces pending approvals a human must decide, never a direct write. Wrap the returned block id with create_agent to make it a runnable Library agent. Defining a block runs nothing and spends nothing.
  - `graph`objectoptionaltyped-IO node graph: {inputs:[{name,type}], nodes:[{id, kind, inputs:[{name,type}], output:{name,type}, config?, after?:[upstream node ids]}], outputs:[{name,type}]}
  - `name`stringoptionalhuman-readable block name
- `get_agent`member+ Return one agent's registry metadata (title, intent, template flag, grounding policy, and the composed block it wraps).
  - `agent_id`stringoptionalagent uuid
- `get_agent_ports`member+ Return the input and output ports an agent's block declares (name, type, required), so an agent column's input_bindings and output_bindings can be wired by name instead of guessed. A read; spends nothing.
  - `agent_id`string (uuid)optionalagent uuid
- `list_agents`member+ List the grounded agents in the active workspace's Library, grouped by intent (monitor / investigate / act). Each agent runs a governed, evidence-backed automation. No arguments.
- `run_agent`member+ Run an agent once against one input record. Read outputs return inline with provenance; write outputs are persisted as pending approvals and never auto-published (govern via list_approvals / decide_approval).
  - `agent_id`stringoptionalagent uuid
  - `input`objectoptionaloptional input record of typed port values
- `set_schedule`member+ Irreversibly attach or update unattended recurrence for exactly one Agent or Sheet owner, or cancel future occurrences with enabled=false. This public tool is refused inside composed blocks.
  - `agent_id`stringoptionaldirect Agent owner uuid; omit for a Sheet schedule
  - `cadence`stringoptionalUTC cron expression or cadence descriptor; required when enabling
  - `enabled`booleanoptionaltrue to enable/update; false to cancel an existing schedule
  - `sheet_id`stringoptionalSheet owner uuid; omit for a direct Agent schedule

****Composed blocks**Define and operate the typed-IO node graphs agents are built from, including dry runs and schedules.*9***

- `cancel_block_run`member+ Request tenant-user cancellation of one queued or running workflow.
  - `run_id`string (uuid)optionaltenant-owned queued or running workflow uuid
- `dry_run_block`member+ Estimate credits and enumerate writes for a published block without spending or executing.
  - `block_id`string (uuid)optionalpublished composed-block uuid
  - `input`objectoptionaloptional typed input port values
- `get_block_run`member+ Get one tenant workflow run with audit events, node receipts, and evidence.
  - `run_id`string (uuid)optionaltenant-owned workflow run uuid
- `list_block_runs`member+ List tenant workflow runs with immutable attempt and event history.
  - `limit`integer, 1 to 100optional
- `list_block_schedules`member+ List tenant workflow schedules, exact inputs, and enabled state. No arguments.
- `list_blocks`member+ List composed blocks in the active workspace, including graph, status, and version. No arguments.
- `retry_block_run`member+ Queue the next bounded immutable attempt for a failed workflow run.
  - `run_id`string (uuid)optionaltenant-owned failed or canceled workflow uuid
- `run_block`member+ Queue a published composed block once; write outputs remain pending human approval.
  - `block_id`string (uuid)optionalpublished composed-block uuid
  - `idempotency_key`stringoptionaloptional stable admission key, at most 200 bytes
  - `input`objectoptionaloptional typed input port values
  - `quote_ref`stringoptionalexact quote_ref returned by dry_run_block
- `schedule_block`member+ Create, update, or disable a recurring workflow with exact typed input.
  - `block_id`string (uuid)optionalpublished composed-block uuid
  - `cadence`stringoptionalUTC cron, descriptor, or disabled
  - `input`objectoptionaloptional typed input port values

****Agent runs**Read, retry, and cancel agent runs.*4***

- `cancel_run`member+ Cancel an Agent or Sheet run through the canonical durable workflow lifecycle.
  - `run_id`stringoptionalAgent run uuid
- `get_run`member+ Return one run and its produced outputs: read outputs with provenance, and write outputs that became approvals.
  - `run_id`stringoptionalrun uuid
- `list_runs`member+ List recent agent runs in the workspace, newest first, optionally scoped to one agent. Each run carries status, credit cost, and provenance.
  - `agent_id`stringoptionaloptional agent uuid to scope the history
  - `limit`integer, 1 to 200optionalmax runs (1..200)
- `retry_run`member+ Retry a failed or canceled Agent or Sheet run as an immutable child after revalidating live owner, feature, block revision, spend, and policy gates.
  - `run_id`stringoptionalfailed or canceled Agent run uuid

****Agent schedules**List the schedules attached to agents and blocks.*1***

- `list_schedules`member+ List Agent and Sheet recurrences with exact ownership, next occurrence, enabled state, and latest run outcome. No arguments.

****Approvals**Read and decide the pending approvals a write-posture output produces. A human decides; the platform never writes on its own.*2***

- `decide_approval`member+ Record a human accept / edit / discard on a pending approval. This only records the verdict; nothing is published (no auto-publish in v1).
  - `approval_id`stringoptionalapproval uuid
  - `decision`stringoptionalthe human verdict
- `list_approvals`member+ List write-posture approvals awaiting a human decision (the Act inbox, filtered to agent-originated writes). Optionally filter by state.
  - `state`stringoptionaloptional approval state filter

****Brand kits and skills**Read and create the reusable context an agent grounds on.*7***

- `create_brand_kit`member+ Store a Context brand kit for the active workspace.
  - `content`objectoptionalvoice / positioning / guardrails payload
  - `name`stringoptionalbrand kit name
- `create_skill`member+ Store a Context skill (a reusable instruction/persona fragment) for the active workspace.
  - `instruction`stringoptionalthe reusable instruction/persona text
  - `name`stringoptionalskill name
- `get_brand_kit`member+ Return one Context brand kit by id.
  - `brand_kit_id`stringoptionalbrand kit uuid
- `get_skill`member+ Return one Context skill by id.
  - `skill_id`stringoptionalskill uuid
- `list_brand_kits`member+ List the workspace's Context brand kits (voice, positioning, guardrails) that ground an agent's writes. No arguments.
- `list_context`member+ List the workspace grounding inventory: Knowledge Sources, brand kits, segments, and reusable skills. No arguments.
- `list_skills`member+ List the workspace's Context skills (reusable instruction/persona fragments an agent references). No arguments.

****Tables and grid**Build and drive a grid table: columns, rows, cells, runs, estimates, and export. Every twin calls the same service the Tables UI calls.*23***

- `add_sheet_column`member+ Add one typed column to a grid table. Value kinds (text, long_text, number, boolean, select, multi_select, date, url) hold what a person or an agent writes: a url column stores one absolute http:// or https:// address as text, and a multi_select column stores an array of the options named in config.options (null clears it; the empty array is refused). The column is appended unless before_column_id places it. prompt, topic, and source columns hold the workspace's own tracked data as {"id","label"} values; an agent column runs one agent per row, reading its inputs from the columns named in input_bindings and writing its outputs into the columns named in output_bindings (use get_agent_ports to see the port names). A tool column runs ONE read-postured MCP tool per row with no agent and no model: name the tool in config.tool, map each of its arguments to a column through input_bindings (the binding key is the argument name), and bind the result port, which is the tool's own name, through output_bindings. Only a curated list of tools can back a column, and the refusal names them. An input binding may carry a PATH, which is how one column reads a single field out of another column's JSON instead of handing the whole blob downstream: a drafting column returning {"title","markdown"} feeds a scorer that wants those as two separate arguments. See input_bindings for the grammar. A draft column writes an article draft into the drafts inbox once per row, and is the only kind here that costs a model call: name the mode in config.mode, either "create" to write a new draft or "revise" to rewrite the one an earlier draft column already wrote. A create column reads source_url (required, the page currently winning the query), coverage_queries, prompt_id and prompt_text; a revise column reads draft and instructions, both required. Both write the port named draft, one JSON object of {draft_id,title,markdown,status}, so a later column reads title or markdown out of it at a path. A formula column is a JavaScript expression over the row's other columns and is free at any row count. Adding a column of any kind runs nothing and spends nothing.
  - `agent_id`string (uuid)optionalagent uuid; required for kind=agent, refused for every other kind
  - `before_column_id`stringoptionalcreate the column immediately before this column uuid, in one transaction; omitted or the literal "end" appends
  - `config`objectoptionalkind-specific config. select and multi_select: {"options":["a","b"]}. agent: optional {"run_condition":{"column_id":"<value column uuid>","op":"is_empty|not_empty|equals|not_equals|contains|gt|lt","value":<scalar, omitted for the emptiness checks>}}, evaluated per row at run time; a row that fails it is recorded as skipped at zero spend. A run_condition may instead be COMPOUND: {"mode":"all"|"any","conditions":[<1 to 4 of the same predicate object>]}; mode defaults to "all" when omitted, and mixing the single-predicate fields with a conditions list is refused. tool columns carry {"tool":"<tool name>"} here, which is the tool the column runs and the one key the column adds; a tool column takes no depends_on. agent columns also declare their place in a chain here, with {"depends_on":["<upstream agent column uuid>"]}: an ORDER edge meaning "run me after that column", not a value edge, so it is set on the DOWNSTREAM column and lists its upstreams (at most 16). Both keys may sit in the same config object, and sending config replaces the whole object, so carry the keys you are not changing. The value path is separate and still required: the upstream must write a value column through its output_bindings that this column reads through its own input_bindings or run_condition, or the dependency is refused as decoration. A dependency on a value column, on itself, or one that closes a cycle is refused too. Declaring one runs nothing and spends nothing; estimate_sheet_chain then prices the whole chain and run_sheet_chain runs it in dependency order.
  - `input_bindings`objectoptionalagent and tool columns only: agent input port name, or tool argument name, -> the column that supplies it. Each value is EITHER a bare column uuid, meaning the whole cell, OR {"column_id":"<uuid>","path":"<path>"}. A path reads ONE field out of that column's value instead of the whole cell: dots for object keys and brackets for list positions, like "summary.verdict" or "checks[0].instruction". No wildcards, no filters, no slices, no functions, and at most 8 levels deep. A path landing on an object or a list binds that object or list as it is, with no flattening. Read a cell with get_sheet first and write the path against what is actually in it; a path that stops resolving fails that row's cell naming the path and the column.
  - `kind`stringoptionalcolumn type
  - `name`stringoptionalcolumn header
  - `output_bindings`objectoptionalagent and tool columns only: agent output port name, or the tool's own name, -> the column that receives it. Each value is a column uuid, or {"column_id":"<uuid>"}. An output writes the whole cell, so a path here is refused: to use one field of what it wrote, put the path on the binding of the column that READS it.
  - `sheet_id`string (uuid)optionalgrid table uuid
- `add_sheet_rows`member+ Append empty rows to a grid table (or insert them directly below after_row_id). The table must already have at least one column. Returns the created rows with their ids for set_sheet_cell. Runs nothing and spends nothing.
  - `after_row_id`string (uuid)optionaloptional row uuid to insert below; omitted appends at the end
  - `count`integer, 1 to 500optionalhow many rows to add (default 1)
  - `sheet_id`string (uuid)optionalgrid table uuid
- `cancel_sheet_cell`member+ Return one QUEUED agent cell to idle, releasing its reservation against the table's ceiling. Free: cancel never spends. A running cell is refused with canceled=false and a reason (the worker owns it and it finishes on its own); a cell with no queued run is refused the same way. A refusal is an answer, not an error.
  - `column_id`string (uuid)optionalagent column uuid
  - `row_id`string (uuid)optionalrow uuid
- `create_sheet`member+ Create a table. With grid=true this creates a BLANK spreadsheet table (a grid): no rows, no fan-out, and a credit estimate of zero; shape it with add_sheet_column and add_sheet_rows, fill cells with set_sheet_cell, and run agent columns with run_sheet_cell or run_sheet_column. Without grid this plans a bulk fan-out of one agent over explicit entities, typed records, or a saved segment, and returns the draft sheet, its queued rows, and a pre-run credit estimate. Creating a table of either kind runs nothing and spends nothing; run_sheet runs a planned fan-out.
  - `agent_id`stringoptionalagent uuid: the backing agent of a grid table, or the agent to fan across the rows
  - `concurrency_cap`integer, 1 to 64optionalbounded parallelism (1..64)
  - `grid`booleanoptionalcreate a blank spreadsheet table instead of planning a fan-out; row_source is ignored
  - `name`stringoptionaloptional sheet name
  - `row_source`objectoptionalrequired unless grid=true: kind=entity-set with entities[], kind=record-set with records[], or kind=segment with segment_id
- `delete_sheet`member+ Retire a table. It leaves the library and every read of it, while its runs and evidence stay reachable through list_runs and get_run. A queued or running table is refused. There is no restore through this tool. Spends nothing.
  - `sheet_id`string (uuid)optionaltable uuid to retire
- `delete_sheet_column`member+ Delete one column and every cell and run state stored against it. Permanent; there is no undo through this tool. Spends nothing.
  - `column_id`string (uuid)optionalcolumn uuid
- `delete_sheet_row`member+ Delete one row and every cell and run state stored against it. Permanent; there is no undo through this tool. Spends nothing.
  - `row_id`string (uuid)optionalrow uuid
- `duplicate_sheet`member+ Copy a table into a fresh draft named "<name> copy": its columns, rows, and user-authored cells copy; agent results, cell runs, and run history stay with the source. A queued or running table is refused. Spends nothing.
  - `sheet_id`string (uuid)optionaltable uuid to copy
- `duplicate_sheet_row`member+ Copy one row's user-authored values into a new row directly below it. Agent results and run history are not copied, because evidence is never attributed to a row that did not run. Spends nothing.
  - `row_id`string (uuid)optionalrow uuid to copy
- `estimate_sheet_chain`member+ Price a whole chain, not one column: every stage that feeds the named column, over every row, before anything is claimed. Returns the per-stage projection, the total, the table's credit ceiling and what it has already admitted. A row that stops early is never priced for the stages it will not reach. A read: claims nothing, queues nothing, spends nothing.
  - `column_id`string (uuid)optionalterminal agent column uuid; every stage feeding it is priced
  - `mode`stringoptionalwhich cells a column sweep may claim: unrun_or_failed (default) takes idle, failed, and skipped cells and never re-spends a done cell; force also re-runs done cells. Queued and running cells are refused in every mode.
- `estimate_sheet_column`member+ Price what running one agent column would cost, without running it. estimated_credits is the whole gesture's price: the named column PLUS every column that declares it runs after it, since running the first starts the rest. stages breaks that down per column and is exactly the set the run may touch; runnable_cells is the named column's own count. credits_per_cell is what ONE cell of that gesture costs, cascade included, which is the number a single-cell Run spends. Also returns the table's credit ceiling and what it has already admitted against it. A read: claims nothing, queues nothing, spends nothing.
  - `column_id`string (uuid)optionalagent column uuid
  - `mode`stringoptionalwhich cells a column sweep may claim: unrun_or_failed (default) takes idle, failed, and skipped cells and never re-spends a done cell; force also re-runs done cells. Queued and running cells are refused in every mode.
- `export_sheet`member+ Return a grid table's full contents as CSV text: a Row number, then one column per stored column in position order, agent columns printing each cell's run status. The same file the REST CSV export streams, bounded at 262144 bytes: past that the text stops at the last complete row and truncated=true, and the complete file is the in-app download. Refused while the table is queued or running, or while any of its cells is queued or running, so the export is never a torn snapshot. A legacy batch table is refused too; read it with get_sheet. XLSX is not offered here. A read; spends nothing.
  - `sheet_id`string (uuid)optionaltable uuid to export
- `get_sheet`member+ Return a sheet and its per-row fan-out state. Result artifacts are omitted by default to keep polling and MCP context bounded; set include_artifacts=true to include byte-bounded output previews, provenance, approvals, and run links. Open the linked run to view or download a complete truncated value.
  - `include_artifacts`booleanoptionalinclude completed-row outputs, provenance, and approvals
  - `sheet_id`stringoptionalsheet uuid
- `list_sheets`member+ List the workspace's bulk sheets, newest first (the Sheets history), so a sheet stays findable without its deep link. No arguments.
- `move_sheet_column`member+ Reorder one column, either one step by direction (left or right) or to a destination with before_column_id (a column uuid, or the literal "end" to place it last). A destination wins over a direction when both are sent. Spends nothing.
  - `before_column_id`stringoptionalland immediately before this column uuid, or "end" for last
  - `column_id`string (uuid)optionalcolumn uuid to move
  - `direction`stringoptionalone-step move
- `move_sheet_row`member+ Reorder one row, either one step by direction (up or down) or to a destination with before_row_id (a row uuid, or the literal "end" to place it last). A destination wins over a direction when both are sent. Spends nothing.
  - `before_row_id`stringoptionalland immediately above this row uuid, or "end" for last
  - `direction`stringoptionalone-step move
  - `row_id`string (uuid)optionalrow uuid to move
- `rename_sheet`member+ Rename a table and/or move its lifetime credit ceiling (100..25000 credits), the fence every cell admission is checked against. At least one of name or credit_ceiling is required. Changing the ceiling is a spend control, so it is refused unless the caller's role is owner or admin; renaming is open to every member. Lowering the ceiling below what the table has already admitted cancels nothing and simply refuses further runs. Spends nothing.
  - `credit_ceiling`integer, 100 to 25000optionalnew lifetime credit ceiling (owner or admin only)
  - `name`stringoptionalnew display name
  - `sheet_id`string (uuid)optionaltable uuid
- `run_sheet`member+ Accept a planned sheet for durable background fan-out with server-enforced concurrency and credit caps. Returns acceptance plus the sheet's current state; use get_sheet to follow row progress.
  - `sheet_id`stringoptionalsheet uuid
- `run_sheet_cell`member+ Run one agent column for one row. SPENDS CREDITS when admitted: the cell is queued for durable execution and its dry-run estimate is reserved against the table's credit ceiling. It also starts this row's cells in every column that declares it runs after this one, which estimate_sheet_column prices as part of the same gesture. If the column carries a run_condition the row fails, the cell is recorded as skipped at zero spend. accepted=false with a reason (ceiling reached, already running, condition skipped) is a real answer, not an error, and means nothing ran and nothing was spent. Poll get_sheet for the result.
  - `column_id`string (uuid)optionalagent column uuid
  - `row_id`string (uuid)optionalrow uuid
- `run_sheet_chain`member+ Run a whole chain from one call: every stage feeding the named column, in dependency order, admitting only the cells whose own upstreams are done for that row. Rows advance independently, so a slow row never holds another back. The chain is priced whole first and refused whole if the table cannot afford it, because part-funding a pipeline that cannot finish is worse than refusing it. A stage whose upstream is empty records a skip at zero spend.
  - `column_id`string (uuid)optionalterminal agent column uuid; every stage feeding it runs
  - `mode`stringoptionalwhich cells a column sweep may claim: unrun_or_failed (default) takes idle, failed, and skipped cells and never re-spends a done cell; force also re-runs done cells. Queued and running cells are refused in every mode.
- `run_sheet_column`member+ Run one agent column across every runnable row, and the columns wired to run after it. SPENDS CREDITS for every admitted cell. Each admitted cell is priced by a dry run and reserved against the table's credit ceiling before anything runs; a refusal names the reason and spends nothing. Returns how many cells were admitted in the named column, how many the run_condition skipped (zero spend), and a per-row refusal list; refusals are answers, not errors. Downstream columns are admitted as each row's upstream finishes, so they report nothing here. Call estimate_sheet_column first: it prices this whole gesture, not just the named column.
  - `column_id`string (uuid)optionalagent column uuid
  - `mode`stringoptionalwhich cells a column sweep may claim: unrun_or_failed (default) takes idle, failed, and skipped cells and never re-spends a done cell; force also re-runs done cells. Queued and running cells are refused in every mode.
- `set_sheet_cell`member+ Write one user-authored cell value in a value column (never an agent column). value is any JSON scalar; null clears the cell. A select column accepts only one of its configured options; a prompt, topic, or source column accepts exactly {"id":"...","label":"..."} naming data the workspace tracks (prompt and topic ids are uuids, a source id is its domain; the label is replaced with the canonical one) and refuses anything else. Runs nothing and spends nothing.
  - `column_id`string (uuid)optionalvalue column uuid
  - `row_id`string (uuid)optionalrow uuid
  - `value`anyoptionalthe JSON value to store; null clears the cell
- `update_sheet_column`member+ Edit one column: rename it, retype it between value kinds, replace its config (a select column's options, an agent column's run_condition), or rewire an agent column's agent and bindings. Absent fields are left unchanged; an empty bindings object clears that wiring. A retype leaves stored cell values as they are, and rewiring resets nothing retroactively: finished results stand and only future runs use the new wiring. A change to or from the agent kind is refused. Sending input_bindings replaces the whole map, paths included, so re-send the path on any binding that carried one or it reverts to reading the whole cell. Runs nothing and spends nothing.
  - `agent_id`string (uuid)optionalagent columns only: the agent to run from now on
  - `column_id`string (uuid)optionalcolumn uuid
  - `config`objectoptionalkind-specific config. select and multi_select: {"options":["a","b"]}. agent: optional {"run_condition":{"column_id":"<value column uuid>","op":"is_empty|not_empty|equals|not_equals|contains|gt|lt","value":<scalar, omitted for the emptiness checks>}}, evaluated per row at run time; a row that fails it is recorded as skipped at zero spend. A run_condition may instead be COMPOUND: {"mode":"all"|"any","conditions":[<1 to 4 of the same predicate object>]}; mode defaults to "all" when omitted, and mixing the single-predicate fields with a conditions list is refused. tool columns carry {"tool":"<tool name>"} here, which is the tool the column runs and the one key the column adds; a tool column takes no depends_on. agent columns also declare their place in a chain here, with {"depends_on":["<upstream agent column uuid>"]}: an ORDER edge meaning "run me after that column", not a value edge, so it is set on the DOWNSTREAM column and lists its upstreams (at most 16). Both keys may sit in the same config object, and sending config replaces the whole object, so carry the keys you are not changing. The value path is separate and still required: the upstream must write a value column through its output_bindings that this column reads through its own input_bindings or run_condition, or the dependency is refused as decoration. A dependency on a value column, on itself, or one that closes a cycle is refused too. Declaring one runs nothing and spends nothing; estimate_sheet_chain then prices the whole chain and run_sheet_chain runs it in dependency order.
  - `input_bindings`objectoptionalagent columns only: replacement input wiring, whole-map; {} clears it. Each value is EITHER a bare column uuid, meaning the whole cell, OR {"column_id":"<uuid>","path":"<path>"}. A path reads ONE field out of that column's value instead of the whole cell: dots for object keys and brackets for list positions, like "summary.verdict" or "checks[0].instruction". No wildcards, no filters, no slices, no functions, and at most 8 levels deep. A path landing on an object or a list binds that object or list as it is, with no flattening. Read a cell with get_sheet first and write the path against what is actually in it; a path that stops resolving fails that row's cell naming the path and the column.
  - `kind`stringoptionalnew value kind (an agent, tool, or draft column cannot change kind)
  - `name`stringoptionalnew column header
  - `output_bindings`objectoptionalagent columns only: replacement output wiring, whole-map; {} clears it. Each value is a column uuid, or {"column_id":"<uuid>"}. An output writes the whole cell, so a path here is refused: to use one field of what it wrote, put the path on the binding of the column that READS it.

****Agency portfolio**Group client properties you already belong to, read their visibility side by side, and manage the shared purse.*14***

- `attach_property_to_portfolio`owner+ Attach this workspace (the organization this API key belongs to) to a portfolio you hold a portfolio membership on. Attaching a different organization needs that organization's own key.
  - `portfolio_id`string (uuid)optionalportfolio uuid
- `create_portfolio`member+ Create an empty portfolio owned by you. It groups client properties without owning them; attach properties afterwards with attach_property_to_portfolio.
  - `name`stringoptionalportfolio display name, 1 to 120 characters
- `delete_portfolio`owner+ Permanently delete a portfolio you hold the portfolio Owner role on. The grouping and its memberships are removed with no undelete path; client organizations and their data are untouched.
  - `portfolio_id`string (uuid)optionalportfolio uuid
- `detach_property_from_portfolio`owner+ Detach this workspace (the organization this API key belongs to) from a portfolio you hold the portfolio Owner role on. Re-attaching restores it exactly.
  - `portfolio_id`string (uuid)optionalportfolio uuid
- `get_portfolio`member+ Read one portfolio's summary: identity, the count of client properties you may see, and whether brands exist in it that you cannot see. Omit portfolio_id when you belong to exactly one portfolio.
  - `portfolio_id`string (uuid)optionalportfolio uuid; optional when you belong to exactly one portfolio
- `get_portfolio_auto_recharge`owner+ Read the portfolio's auto-recharge configuration: enabled, threshold, top-up amount, the hard monthly ceiling (all in credits), the banked month spend, whether a payment method is saved, and whether recharging is paused. Owner only. Omit portfolio_id when you belong to exactly one portfolio.
  - `portfolio_id`string (uuid)optionalportfolio uuid; optional when you belong to exactly one portfolio
- `get_portfolio_credits`member+ Read the portfolio's shared purse: balance in credits, a low-balance flag, and recent funding movements (top-ups and grants; never a client's consumption). Omit portfolio_id when you belong to exactly one portfolio.
  - `portfolio_id`string (uuid)optionalportfolio uuid; optional when you belong to exactly one portfolio
- `get_portfolio_rollup`member+ Read the cross-client visibility roll-up for one portfolio: per-client visibility, basis counts, deltas, tracked prompts and last-run recency, over the clients you may see. Omit portfolio_id when you belong to exactly one portfolio.
  - `days`integer, 1 to 90optionaltrailing window in days (1..90)
  - `order`stringoptionalsort direction; defaults to asc
  - `portfolio_id`string (uuid)optionalportfolio uuid; optional when you belong to exactly one portfolio
  - `sort`stringoptionalrow ordering key; defaults to visibility_delta_pct
- `get_portfolio_usage`member+ Read the per-client credit statement for one portfolio over a trailing window: per visible client, credits consumed, the split between purse-funded and self-funded, and per-feature line items, plus totals over the visible clients only. Omit portfolio_id when you belong to exactly one portfolio.
  - `days`integer, 1 to 90optionaltrailing window in days (1..90)
  - `portfolio_id`string (uuid)optionalportfolio uuid; optional when you belong to exactly one portfolio
- `leave_portfolio`owner+ Remove an organization you own from whatever managed portfolio it belongs to, naming no portfolio. Succeeds whether or not it was attached, so a new agency can attach it afterwards.
  - `organization_id`string (uuid)optionaluuid of an organization you own; the one organization argument on this surface
- `list_portfolios`member+ List every portfolio you hold a portfolio membership on: id, name and timestamps only. A portfolio you can open through an organization membership alone is never listed. No arguments.
- `rename_portfolio`owner+ Rename a portfolio you hold the portfolio Owner role on. Rename only; nothing else about the portfolio changes.
  - `name`stringoptionalnew display name, 1 to 120 characters
  - `portfolio_id`string (uuid)optionalportfolio uuid
- `set_portfolio_auto_recharge`owner+ Configure the portfolio's auto-recharge: enable or disable topping up the purse from the portfolio's saved card. Enabling requires an explicit positive ceiling_credits (the aggregate monthly ceiling; there is no unlimited setting) and a saved payment method. All amounts are in credits. Owner only.
  - `amount_credits`integer, 0 or moreoptionalcredits added per recharge
  - `ceiling_credits`integer, 0 or moreoptionalhard aggregate monthly ceiling in credits; required and positive when enabling
  - `enabled`booleanoptionalwhether auto-recharge is on
  - `portfolio_id`string (uuid)optionalportfolio uuid of the purse being configured
  - `threshold_credits`integer, 0 or moreoptionalrecharge when the purse balance drops below this many credits
- `set_portfolio_funding`owner+ Point this workspace's metered spend at the portfolio's shared purse (funded: true) or back at its own balance (funded: false). Flips this API key's own organization only; funding a different organization needs that organization's own key. Requires the portfolio Owner role.
  - `funded`booleanoptionaltrue to fund this workspace from the purse, false to stop
  - `portfolio_id`string (uuid)optionalportfolio uuid of the purse

****Spend**Read current LLM spend against the configured caps.*1***

- `get_spend_summary`member+ Return today's LLM cost in cents, month-to-date cost, the configured daily + monthly budgets, per-platform breakdown for today, and a day-by-day series for the trailing window. Use to answer 'how much have I spent this month' or 'why did the cron stop'.
  - `days`integer, 1 to 30optionaltrailing window in days (1..30)

****Organization administration**Owner-only identity, membership, and budget configuration.*3***

- `list_org_members`owner+ OWNER ONLY. List active members of the active property with their email, role, and join date. No arguments.
- `set_spending_caps`owner+ OWNER ONLY. Set the daily and monthly cost budgets in CENTS for LLM evaluation runs. 0 means NO SPENDING in that window, not unlimited. The cron scheduler will short-circuit (writing status='failed' with reason='budget_exceeded') once a daily run would push spend past these caps.
  - `daily_budget_cents`integer, 0 or moreoptionaldaily cap in cents (0 blocks all spending)
  - `monthly_budget_cents`integer, 0 or moreoptionalmonthly cap in cents (0 blocks all spending)
- `update_brand_identity`owner+ OWNER ONLY. Update the active property's brand name and / or website domain. These two fields drive all downstream brand-mention parsing and ownership detection on citations.
  - `name`stringoptionalthe brand display name
  - `website_domain`stringoptionalthe canonical website domain (no scheme)

04 · External agent logs

## Bring crawler evidence from the edge.

Agent-log ingestion uses a dedicated source token, not your MCP API key. An owner creates a source, stores its one-time `lmlog_…` credential in the sending platform, and validates the first known-bot request in the Crawlers view.

[Open Crawlers setup](https://limelit.co/signup?redirect_to=/crawlers)

POST`/rest/v1/agent-logs/ingest`

**Strict JSON batch ingest**

### Request

```
curl --request POST \
  https://api.limelit.co/rest/v1/agent-logs/ingest \
  --header "Authorization: Bearer <LIMELIT_SOURCE_TOKEN>" \
  --header "Content-Type: application/json" \
  --data '{
    "delivery_id": "collector:2026-07-10T20:01:11Z:batch-001",
    "entries": [{
      "timestamp": "2026-07-10T20:01:11Z",
      "request_method": "GET",
      "request_url": "https://example.com/docs/mcp",
      "response_status": 200,
      "user_agent": "<ORIGINAL_USER_AGENT>",
      "country_code": "US"
    }]
  }'
```

### Success

```
{
  "accepted": 1,
  "dropped_non_bot": 0
}
```

Authentication

Bearer source token

Content type

`application/json`

Body limit

2 MiB

Batch limit

500 entries, atomic

Source limit

60 requests/minute, shared

Rate metadata

`X-RateLimit-*` headers

### Envelope and entry schema

`delivery_id`optionalStable 1–128 byte retry key; same payload replays safely for seven days

`timestamp`requiredRFC3339 timestamp

`request_method`requiredHTTP method token

`request_url`requiredAbsolute HTTP(S) URL or rooted path

`response_status`requiredInteger from 100 to 599

`user_agent`requiredOriginal request user agent

`country_code`optionalTwo-letter country code

`client_ip`optionalValidated and discarded; never stored

`referer`optionalAbsolute HTTP(S) URL or rooted path

### Response contract

**200**Accepted and dropped counts.

**400**Malformed, trailing, or unknown JSON fields.

**401**Missing, invalid, or revoked source token.

**409**A delivery ID was reused for different entries.

**413**Body or batch exceeds a hard limit.

**422**One or more entries fail field validation.

**429**Authentication or source rate limit reached; inspect reset headers.

**503**Shared limiter state is unavailable; the body was not read.

**Abuse backstop:** Malformed source credentials are rejected locally. A bounded cluster-wide lookup gate runs before valid-format tokens reach storage, then the authenticated source receives its own shared 60-request budget.

**Privacy and classification boundary**

Limelit strips URL credentials, query strings, and fragments before persistence and stores only rows that match its known-bot registry. User-agent matching is a traffic classification signal, not proof that a request came from the named crawler; user agents can be spoofed. The published adapters omit client IP addresses. Known-bot rows expire 90 days after Limelit receives them, regardless of their event timestamp.

Adapter 01 · Cloudflare

### Worker Route proxy

Put a module Worker Route in front of the existing origin. The template preserves the origin response and forwards a bounded known-bot event with `waitUntil`. Do not attach it as a Custom Domain, which can recurse when the Worker fetches origin.

1. Create a `cloudflare` source and store the token with `wrangler secret put LIMELIT_LOG_TOKEN`.
2. Deploy the Worker and bind a narrow production route over the existing origin.
3. Send one known-bot test request and confirm the unchanged origin response plus the normalized path in Agents.

[Download worker.ts](https://limelit.co/integrations/agent-logs/cloudflare-worker/worker.ts) [Download Wrangler example](https://limelit.co/integrations/agent-logs/cloudflare-worker/wrangler.toml.example)

**Delivery:** direct mode is best-effort and at-most-once. A 429 is logged and not retried. For sustained volume above the source limit, buffer with a queue and send atomic batches of at most 500.

[Fetch handler ↗](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) [waitUntil ↗](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil) [Worker Routes ↗](https://developers.cloudflare.com/workers/configuration/routing/routes/)

Adapter 02 · Vercel

### Log Drain collector

Deploy the App Router handler in a small collector project that is excluded from the Drain's selected projects. The adapter verifies the raw HMAC-SHA1 signature before parsing and translates one delivery into one atomic Limelit batch.

1. Set `LIMELIT_LOG_TOKEN` and `VERCEL_DRAIN_SIGNATURE_SECRET` as encrypted environment variables.
2. Create a Logs / JSON custom Drain pointed at the collector and select only the production projects to measure.
3. Run Vercel's endpoint test, then confirm a known-bot request in Agents.

[Download route.ts](https://limelit.co/integrations/agent-logs/vercel-drain/route.ts)

**Delivery:** Vercel is at-least-once. The adapter hashes the signed raw body into a seven-day idempotency key, so an ambiguous matching retry returns the original counts without inserting twice. Bodies above 2 MiB or deliveries above 500 known-bot rows are rejected before any rows are written.

[Configure Drains ↗](https://vercel.com/docs/drains/using-drains) [JSON log schema ↗](https://vercel.com/docs/drains/reference/logs) [Signature security ↗](https://vercel.com/docs/drains/security)

MCP calls`lmlt_… personal API key`

Log ingest`lmlog_… source token`

Do not substitute one credential type for the other.
