[`, where you read it in full and act on it. A row's ref is the same everywhere: in the Inbox, on Home, in emails, and in the API and MCP.
## Three kinds of row
Every row waiting on you asks one of three things of you. We work out which, not the agent and not the screen, so the Inbox, Home, the morning brief, the daily email, the API and MCP all agree on what a row is asking.
| Kind | What it asks | What you do |
| - | - | - |
| **Approve** | We wrote something and want a yes or a no: a proposed change, an article, or a run that stopped at the [approval gate](/agent/approval) | Approve, edit then approve (for the kinds that allow it), or decline with a reason |
| **Answer** | We need a fact only you have | Type the answer, or pick one of the answers offered. It is saved and the run that asked carries on |
| **Do on your side** | Something only you can do somewhere else: connect an integration, add a DNS record, paste new wording into your CMS | Follow the steps, then press **Done**. When we can check it, we do |
A few rows are notices: they tell you something, such as credits we added back to your balance, and ask nothing. They are listed last and never count as a decision.
## At most three decisions first
A row is one attempt; a decision is one thing for you to decide or do. Seven runs that hit the same missing GitHub connection are seven rows and **one** decision, shown as **Connect GitHub · 7 waiting**. Every row about the same missing integration folds into one decision, and so do approvals waiting on the same tool. Each proposed change is its own decision.
**Waiting on you** shows the three decisions that matter most, ranked in this order:
1. An approval that expires within a day. If it lapses, the run goes on without doing what it asked.
2. How much is at stake: a whole capability switched off, then a failed run, then a run stopped on one thing or an approval that expired, then a judgement call or an unsent rewrite.
3. An approval whose effect cannot be undone, which includes anything that spends money.
4. A question, because an answer is the quickest thing on the list to give and unblocks a run straight away.
5. How many rows are waiting on it.
6. How long it has been waiting, oldest first.
Everything after the first three is grouped by what it asks, such as **2 more questions** or **3 more approvals**, and stays collapsed until you open it.
This one ranking is read everywhere "what needs me" is shown: the Inbox, **Needs you** on Home, the badge, the [morning brief](/agent/morning-brief), the [daily email](#one-email-a-day-at-most), and `view=triage` in the [API](/api/endpoints#get-v1inbox) and [MCP](/mcp/tools#list_inbox). They cannot disagree about what comes first. The badge counts decisions, not rows.
## Needs you on Home
[Home](/start/today#needs-you) leads with the same thing, under **Needs you**: the **top three** decisions from **Waiting on you**, in the same order, each one something you can act on right there: approve or decline a change, or connect what a group of rows is waiting on. A chat paused on a question for you is listed after them. Under the three, Home says how many more are waiting, with a link to the Inbox.
The number beside **Needs you** on Home and the badge on the Inbox button in the top bar are **the same number**, read from one place, so they cannot disagree. It counts decisions, not rows, and adds each chat waiting on your answer. Act on a row on Home and both change together.
## What ends up in Waiting on you
All of these used to be visible only inside a run's transcript.
The agent tried to do something irreversible or hard to undo — publishing a page, sending mail, spending with a third party — and stopped for your explicit yes. The row's page shows the tool, what it would do and whether it can be undone. **Approve once** lets the run make that one call; **Decline** takes a reason and an optional note, which the run reads before it carries on without the call. An approval is never edited; see [why](/agent/approval#approvals-are-answered-as-asked).
An approval that goes unanswered expires after **7 days**. When it does, the run is closed without doing the thing, nothing is changed, and you get a row in the Inbox saying so. See [what the agent shows you](/agent/what-the-agent-shows).
A run that stopped to ask you questions, rather than for permission, is answered in the run itself; the row links there.
Whether an action stops here at all — and how much the agent will do on its own before it does — is set by your workspace's [approval mode](/agent/approval).
The agent needs a fact only you have: a launch date, which plan to lead with, the name your customers use for a feature. You answer in the row. See [Answering a question](#answering-a-question).
The agent tried and could not proceed: a site refused us, a credential is missing, a file it has no write access to. It files what it attempted and what it needs from you, rather than writing "I was unable to…" into a transcript nobody reads. When it knows exactly what you need to do, the row carries numbered steps and, where we can check the result, a check behind **Done**. See [Done, and the check behind it](#done-and-the-check-behind-it).
Several courses of action are defensible and picking one is your call, not a guess the agent should make on your behalf.
In Ask mode, a goal that falls far enough behind to re-plan becomes one of these rows: **"your goal" is behind pace: re-plan the work toward it?** The row starts nothing on its own. It is filed at most once per goal per week, and a dismissed row is not filed again that week. See [Goal re-plans](/agent/decisions#goal-re-plans).
A tool could not run because an [integration](/agent/plugins) is not connected, was revoked, or does not have the permission the action needs. These are raised automatically from a call that actually failed — never from the agent's opinion about what it can do.
All rows about one integration, whether it is not connected or its credential stopped working, from any run or any of its failing tools, are **one row** with an occurrence count. Connecting the integration closes it automatically.
A scheduled run that needs an integration its automation's tool list does not include files nothing here, because connecting it would not help that run. It is noted on the run's transcript; add the tool to the automation to fix it.
Business profile fields the agent needed and found empty share one row: **fill in your business profile**. It is a question: answer it in the row and the answer is saved to the profile field it names. It closes when a profile field is saved.
When the credit balance is spent, or the workspace's monthly credit cap is reached, one row says scheduled work is paused and names the automations. It closes itself when the first one runs again. See [Credits](/account/credits#when-the-balance-is-spent).
A scheduled [automation](/agent/automations) run ended in failure. A chat that fails is failing in front of you; a 3am run is not, so it comes here instead.
One of your [webhook endpoints](/api/webhooks#when-an-endpoint-is-turned-off) kept failing, or answered `410 Gone`, so we stopped sending to it. The row says which host and why, once per endpoint. Turn the endpoint back on in **Settings → Webhooks** once your server answers again, and resend what it missed from its delivery log.
## Repeats and duplicates are one row
Each problem appears once. Three rules make sure of it, and none of them decides anything on your behalf.
**The same problem again.** An automation that hits the same wall every night does **not** produce a row per night. The Inbox recognises a repeat of the same problem and updates the existing row, showing how many times it has now come up. The same question asked twice is one row too.
That count is the useful part. "14 times" means the problem has been there for two weeks and nobody has acted on it — a different fact from the same row appearing for the first time this morning.
Resolving a row closes that chapter. If the same problem happens again afterwards, it comes back as a new row rather than silently reopening one you had already ticked off.
**A better version of the same change.** When the agent proposes a change to something it already has a proposal waiting on — the same file, the same passage of a page, the same article — it revises the waiting row in place rather than filing a second one. The row keeps its link, its activity log reads **Revised: this replaced the earlier version**, and the new version is what you approve. If you had the older version open, your approval is refused with **this was revised since you opened it**, so you never approve words you did not read. A proposal you already approved is left alone; the new version files beside it.
**The same thing in different words.** Decisions, questions that do not fill a profile field, and other free-form rows cannot be matched by what they name, so a run that asks "which launch date?" on Monday and "pick a day for the launch" on Wednesday would file two rows. Every 15 minutes a duplicate check compares each new row of that kind from the last 7 days with the older open rows of the same kind. When it is confident they ask the same thing, it folds the newer row into the older one: the older row counts it as a repeat, and the newer row is hidden.
The duplicate check can only hide a row. It never approves, answers, declines or closes anything, never touches a proposed change or an approval, never compares rows of different kinds, and leaves alone a row you have started to answer or that is being checked. When it is not sure, nothing is hidden.
Every fold can be undone. Open the hidden row's link and you land on the row it was folded into, with **Not the same**: press it and the hidden row is its own row again, and the two are never folded together again. The duplicate check costs you no credits.
## Acting on a row
What you can do depends on what the row is:
| Row | What you can do |
| - | - |
| An approval | **Approve once**, or **Decline** with a reason and an optional note |
| A proposed change, article or post | Approve it, [edit it then approve](#editing-before-you-approve) where the kind allows, or [decline it](#declining-with-a-reason) |
| An approved change with an open pull request | **Mark ready for review** while it is a draft, then **Merge** (see [Merging a pull request](#merging-a-pull-request)) |
| A question | [Answer it](#answering-a-question) |
| Something to do on your side | Follow the steps, then [**Done**](#done-and-the-check-behind-it) |
| Anything the agent raised | **Done** when you have handled it, **Dismiss** if it does not matter, or snooze it until later |
A snoozed row is put away until the time you picked, and comes back to **Waiting on you** on its own — nothing is lost by putting it off.
**Done** is deliberately not called "approve". Nothing is queued up to run when you press it — the work happened outside the product, and you are telling us it is handled. Declining and dismissing are both recorded rather than deleted: what you turn down is as useful a signal as what you accept.
After you act on a row on its own page, the page says what happened and offers **Next**: the row the ranking puts first now.
### Editing before you approve
An article or a LinkedIn post that is still waiting can be edited on its page before you approve it. Press **Edit**, change the words, and **Approve edited** approves your version in the same step. Your version is what publishes, and the agent's original is kept on the item. In the dashboard a [rewrite](#rewrites) has no Approve button: we never publish one, so you copy its new text into your own editor. Over the [API](/api/endpoints) or the [MCP server](/mcp/tools), an approve call can still carry your edit of a rewrite (`edited_value`); that keeps your words on the item, and it is still yours to paste.
* **An edited change is always a claim.** Your words are now what the business says, so an edited change is never treated as a safe change and never goes out on its own: it waits for a person's yes in every [approval mode](/agent/approval), and is never auto-merged.
* **Code is never changed this way.** An edit that adds or changes a fenced code block, a `]` or a `` sample is refused with the rule, and nothing is approved.
* **Your edit gets the same check as a draft.** It is checked against your workspace's rules before it is approved, the same check the agent's own draft gets.
* **An article keeps its front matter.** If the article came with front matter for your site's build and your edit dropped it, it is put back.
* An edited article or post is updated on the content plan too.
A fix to a file in your repository cannot be edited here: edit it in the pull request it opens, or decline it with a note saying what to change. An approval from a paused run cannot be edited either; see [why](/agent/approval#approvals-are-answered-as-asked).
If the agent revised the proposal after you opened it, **Approve** and **Approve edited** are refused with **this was revised since you opened it**, and the page shows the new version. Read it, then approve that.
### Declining, with a reason
Every decline carries one of four reasons:
| Reason | Means |
| - | - |
| **Wrong** | The proposal itself is bad |
| **Not now** | Right idea, wrong time |
| **Already done** | It was handled elsewhere |
| **Never touch this page** | A standing rule about the page, not a verdict on this one proposal |
You can add a note of up to 1,000 characters in your own words. With **Wrong** the note is required, because "wrong" alone does not say what to fix. The reason and the note are kept on the item and in its activity. Declining an approval from a paused run hands the run your reason and note before it carries on without the call.
### Answering a question
Open the row and type your answer, up to 2,000 characters, or pick one of the answers the agent offered. Then:
1. **The answer is saved where every later run reads it.** If the question names a [business profile](/agent/knowledge-and-skills#company) field, the answer is saved to that field, and every other row waiting on that field closes with it. Any other answer is kept as a fact you stated: every run in the workspace reads your newest stated facts, up to 40 of them. There is no screen that lists stated facts.
2. **The row closes,** marked as answered by you.
3. **The run that asked is put back to work,** depending on where it is now:
| The run that asked is… | What happens | What the row says |
| - | - | - |
| Still running | It gets your answer and uses it from its next step | The agent is back on it |
| Finished, failed or suspended | It gets your answer and starts a new turn on it, which spends credits like any turn | The agent is back on it |
| Waiting for you to approve something | It gets your answer and reads it once you answer that approval. It is not restarted behind the approval | Continues after you answer the pending approval |
| Cancelled, or there was no run | Nothing starts: somebody stopped it on purpose. You can start a run with the answer | Nothing is running that was waiting on this |
If the run could not be handed the answer, the answer is still saved and the row stays closed; the row says so and offers **Retry**.
If two people answer at once, the first answer counts and the second is told the row was already answered. If saving fails, nothing changes and you can answer again.
### Done, and the check behind it
A row to do on your side lists numbered steps, with a copy button beside each value you need to paste: a DNS record, a snippet, a line of text. Some rows also carry a check, because there is something we can look at to see it was done:
| Check | What we look at |
| - | - |
| A DNS record | The TXT records on your domain, for the value in the steps |
| A connection | Whether the named integration is connected to this workspace |
| A line on a page | Whether a page on your site shows the text from the steps |
On such a row the button reads **Done, verify it**. Pressing it does not close the row on your word, and you do not wait on a spinner either: the row moves to **Running** as **Checking…**, and the check runs within a few minutes.
* **It passes:** the row closes on its own, saying what was verified.
* **It fails:** DNS and caches often lag a change by minutes, so we try again three more times: after 10 minutes, then 20 minutes after that, then 30 minutes after that, about an hour in all. A check that points off your domain is not retried.
* **It still fails after that:** the row comes back to **Waiting on you** with the reason in plain words, such as "There is no TXT record on example.com yet", and a **Check again** button.
A check only ever looks: a DNS lookup, a read of your connections, a read of one public page. It only looks at your own domain and its subdomains, and a page that redirects off your site does not count. A row cannot be snoozed while its check is running. A row with no check closes when you press **Done**, as it always has.
### The keyboard
The list is keyboard first. Keys are ignored while you are typing in a field, and whenever ⌘, Ctrl or Alt is held.
| Key | Does |
| - | - |
| `j` or `↓` | Next row |
| `k` or `↑` | Previous row |
| `Enter` | Open the row |
| `a` | Approve, where the list can |
| `e` | Open the row to edit it |
| `d` | Open the row to decline it |
| `?` | Show or hide the keys |
| `Esc` | Close the key list |
`a` approves from the list only a single proposed change whose effect can be undone and that is classed safe. Anything else — a claim, a change that cannot be undone, spend, an approval from a paused run, a question, a decision with several rows behind it — says **Open it to approve**, because a yes to something nobody read is not a yes.
### On a phone
On a narrow screen the row's page keeps its main action pinned to the bottom of the screen, so approving is two taps: the row, then **Approve**. The action is reachable without scrolling past a long article or diff, long lines wrap instead of scrolling sideways, and every button is at least 44 pixels tall on a touch screen.
## One email a day, at most
If something has been waiting on you, unseen, for 24 hours, we email you about it. It is **one email per workspace per day**, however many rows are waiting: it lists the top three decisions, in the same order as the Inbox, and how many more are behind them. A row being checked, or one folded into another as a duplicate, is not waiting on you and does not count. These emails are the **Attention** category of your [notifications](/account/notifications).
## The pull request chip
A row whose work ended in a [GitHub pull request](/agent/github) carries a **PR chip**: the pull request number, linked, plus its current state. The chip is how you tell "the agent opened something" from "the agent opened something and it is stuck".
| Chip | What it means |
| - | - |
| **Open** | The pull request is waiting for review. Checks have not failed |
| **Open · checks failing** | Your CI went red on our branch. It is yours to fix — we do not push again |
| **Merged** | It landed. The finding is resolved |
| **Closed** | It was closed without merging. The work item is declined ("closed without merging") and not opened again; reopening the pull request brings it back |
The state comes from GitHub, not from us guessing: it updates as the pull request is reviewed, merged or closed. A merge conflict shows the same way a red build does — surfaced on the row, and left for you.
A failing chip is not a task we are working on. Attensira never pushes a second time to a branch it opened, so nothing changes on that pull request until a person acts on it.
A change that is approved but held by your weekly [change cap](/agent/approval#change-caps) shows as **Approved, held by your weekly limit**.
### Merging a pull request
We merge only when you press **Merge** here, or when you switched on [auto-merge for safe changes](/agent/approval#auto-merge-for-safe-changes). Approving a change opens its pull request and never merges it.
Open an approved item whose pull request is still open and the pull request is shown in full, read live from GitHub: its files and diff, its checks, a deploy preview when there is one, and why it cannot merge yet when it cannot. Then:
1. **Mark ready for review**, while it is a draft. It takes the pull request out of draft and does not merge it. If your repository merges ready pull requests on its own, that automation may merge it from here.
2. **Merge**, once GitHub says it can merge. It squash-merges at the commit on screen.
Both are pinned to the commit you were looking at: if the branch moved since, the click is refused and the view reloads so you review what changed. Once it is out of draft, a conflict, a missing review or a failing check keeps **Merge** disabled with the reason beside it. The chip turns **Merged** when GitHub reports the merge, and the re-reads start then. Merging is dashboard only; the API and MCP cannot merge. The full rules are on the [GitHub page](/agent/github#merging-from-your-inbox).
## Rewrites
A rewrite, shown as **new wording** in the app, is a fix for a page we cannot open a pull request against, because no repository is connected or because that page does not come from one. It is one passage of your live page, quoted exactly as published, beside a better version of it, with the reason and the buying questions it is meant to move.
**We never publish a rewrite.** There is nothing to approve, so a rewrite row has no Approve button. What it has instead:
* **Copy the new text** and paste it into your CMS yourself, then **Mark shipped** with the URL (see [Mark shipped](#mark-shipped)).
* **Share a link**: a private, signed link that expires in 30 days, for forwarding to whoever owns the site. They need no account. The page carries your org's [branding](/agencies/branding). Rotating your workspace's share secret revokes every link at once.
* **Request access from my developer**: see [GitHub](/agent/github#if-you-cannot-install-it-yourself).
The before side is always the page as we actually read it. If the agent quotes a sentence that is not on the page, the rewrite is refused rather than filed. Rewrites carry the same change class as pull requests: one classed **claim** (it touches a price, a number with a unit, a promise, or a competitor's name) says so on the row and on the shared link.
**If a rewrite sits for five days** with nobody copying it and no access request sent, it comes back in **Waiting on you** and in your morning brief. That row goes away the moment you act on it or ask your developer.
## Written for you
Articles and LinkedIn posts the agent writes appear under **Waiting on you**, in **Written for you**, rendered as a document you can read in place. What you can do depends on where the piece goes:
| Action | When it is offered | What it does |
| - | - | - |
| **Copy** | Always | Copies the piece, including any edit you made on the page |
| **Open PR** | A connected repository has a blog directory | Adds the post as a new file in a pull request, with front matter that follows your existing posts. Nothing goes live until you merge it |
| **Send to Notion** | A [Notion blog](/agent/notion) is mapped | Creates a draft page. It is published only if the workspace allows the agent to act |
| **Mark shipped** | Nothing of ours publishes it: a copy-target article, or a LinkedIn post | You give the live URL |
In the list, **Edit** is offered only on a piece you publish yourself (a copy target or a LinkedIn post): the edit stays on the page and **Copy** takes your version. To change the words of a piece bound for a repository or Notion before it goes, open it and use [Edit then **Approve edited**](#editing-before-you-approve), which saves your version on the item. For an article bound for a repository, the front matter fields show as one line above the body.
Articles always wait for a person's yes, in every mode. Revising an article that is still waiting updates the same item. Briefs, emails and notes the agent writes are not filed here. Articles also appear as drafts on the content plan.
**LinkedIn posts** always carry the link to their source page. Attensira never posts to LinkedIn: you post it from your own account and mark it shipped with the post's link.
## Mark shipped
**Mark shipped** is for copy handoffs, where you publish the piece yourself: articles with a copy target, LinkedIn posts, and new wording for an existing page when no repository is connected (next to **Copy the new text** and **Share a link**). It is also how you tell us an article we created as a draft in your [CMS](/agent/cms#new-articles) went live, once you published it there.
* You give the URL where it went live, and the item leaves **Waiting on you**. It is not yet listed in **Done**, which today shows what the agent shipped from its action log; the re-reads below still run.
* The URL is recorded as given. It is not checked until the first re-read, which confirms the change is on that page before measuring anything.
* Marking shipped starts the [before-and-after re-reads](/measure/before-and-after) for the tracked prompts attached to the item, unless a read of your page already saw the change and booked them. See [Page changes](/measure/page-changes).
Approving a copy item does not publish anything. The item comes back with an instruction to paste it and mark it shipped.
## Shipped
The **Done** view lists merged pull requests, placed pitches and delivered changes. Each shows its re-read result in plain words: **Moved** (named more often on X of N prompt and engine pairs, across M answers), **Fell**, **No change**, **Too close to call**, **Too few answers** (not enough data from before the change), **Not live**, or **Re-read booked** with its date. Every result is a single dated reading, never a day-over-day comparison. See [Before and after](/measure/before-and-after) for what each means.
## What the agent can and cannot put here
The agent can raise three things itself: something it is **blocked** on, a **decision** that is yours, and a **question** only you can answer. Each requires it to say what it actually tried. A row it raises can carry at most eight steps, and a check only against your own domain or an integration we offer; anything else is refused and the agent is asked to try again, with nothing filed.
It cannot file the other kinds. A missing connection, a failed run and an expired approval are raised from evidence — a call that really failed, a run that really died, a deadline that really passed — so that everything on this list is a fact rather than a guess.
An hourly pass also files decisions on its own, grouped by the source that caused them — see [What the agent notices on its own](/agent/findings). A change too small to tell from sampling noise is held back rather than filed, and checked again an hour later; see [Noise, or a real change](/agent/findings#noise-or-a-real-change).
Raising something in your Inbox costs no credits and changes nothing in your workspace. The agent is never discouraged from telling you it is stuck.
## The Inbox is not only a screen
Everything above is available through the [MCP server](/mcp/tools#inbox) and the [REST API](/api/endpoints#inbox) as well, so "what needs me right now" is answerable from wherever you already work, not only from the dashboard. Both read the same ranking (`view=triage`), read any row by its ref, answer questions, approve with an edit, decline with a note, and approve or decline a paused run's approval. Every act returns `next_item_ref`, the row the ranking puts first now.
An assistant acting for you should approve, answer or decline only on your instruction, never on its own reading of the row. Two things are not in the MCP tools or the REST API yet: **Not the same** on a folded duplicate, and **Retry** when a run could not be handed your answer. Use the row's page for those.
# Knowledge and skills
Source: https://docs.attensira.com/agent/knowledge-and-skills
Give the agents the facts about your company, reusable skills, and the guardrails every draft is checked against before anyone can approve it.
The business profile is on every plan, and the agent reads and uses it on every plan. Writing your own skills (rules) is on Growth and above.
All of it lives under **Customize** in the sidebar, one pill each:
* **Company**: the facts every draft is written from, your brand and values, and the competitors you track.
* **Skills**: your playbooks, and the skills built into every agent.
* **Guardrails**: your compliance rules, how the agents write, and the words a draft may never use.
The old **Knowledge** address, `/knowledge`, opens Customize → Company. The other two pills, Connectors and Agents, are described on [Connectors](/agent/plugins).
## What does this change?
Knowledge and Skills changes how the agent writes, not what it measures. A brand profile tells it who you are, what you sell, and how you talk. Skills give it reusable instructions to apply every time. Your Share of voice, Citation rate, and Source share are untouched by anything on this page.
Without it, the agent still answers correctly — it just answers generically, and you end up pasting the same context into every [chat](/agent/chats) and every [automation](/agent/automations).
## Company
**Customize → Company** holds the brand profile. The brand profile is the standing context the agent carries into every conversation in the workspace. Put in it the things you would otherwise repeat: what the product does, who buys it, the words you use for your own categories, the competitors you care about, and the claims you are not willing to make.
Be specific and be short. Vague positioning statements make output vaguer. Concrete constraints — "never call it a platform", "always distinguish crawlers from models", "prices are in USD, monthly" — do visible work on every answer that follows.
Write the profile as rules the agent can check its own draft against, not as marketing copy. "Avoid X" is actionable; "we are innovative" is not.
### Drafted for you at setup
When a workspace is set up, Attensira reads your homepage, plus `/about` and `/pricing` when they exist. It drafts only the profile fields those pages actually state, and each draft names the page it came from. Drafts show as **drafted** until you confirm them. A field that already holds a value is never overwritten.
When the agent needs a field that is empty, it raises one row in the [Inbox](/agent/inbox), **fill in your business profile**, shared by every empty field. It is a question: answer it in the row and the answer is saved to the field it names. The row closes when a profile field is saved.
Answers you give to the agent's other questions in the Inbox are kept too, as facts you stated, and every run reads your newest ones, up to 40. There is no screen that lists them.
### Brand and values
Two groups of the profile say who you are rather than what you sell:
* **Brand**: your name as said aloud, your tagline, your colours and your logo URL. Scripts and voice answers use the spoken name, listings reuse the tagline and logo, and images and charts use your colours.
* **Values**: what you stand for, as a list. Drafts are written so they do not contradict it.
Both are optional, so a workspace that leaves them empty keeps its profile coverage. Tone is not a brand field: how the agents write is set by your voice rules under [Guardrails](#guardrails), and the Brand group links there. The name, logo and colour a client sees on a report or page you share are a separate setting, under **Settings → Branding**.
### Competitors
The competitors you track are listed on the Company pill too, and you can add or remove them there. Each tracked competitor adds an **Against *rival*** field to the profile: why a buyer would choose you over them, used on every comparison and alternatives page about them. A competitor you add is measured from the next reading on; see [Competitors](/tracking/competitors).
### Reporting standard
The profile's **Reporting** group has one free-text field, **Reporting standard**: the tone of your reports, who reads them, and words to avoid. Every [client report](/agencies/client-reports) follows it and stores a copy of it. It never overrides the report rules: no percentages, and no movement that a re-read did not prove.
## Skills
**Customize → Skills** lists two kinds: **your playbooks**, which you write, and **Built in**, the skills we maintain. Every agent has the built-in ones; they read your company facts and follow your guardrails. You cannot edit a built-in skill. The list in the app is always the current set.
A skill is a named, reusable instruction for a recurring job: a weekly summary format, the structure of a competitor brief, the tone for a customer-facing note. Instead of restating the format each time, you point the agent at the skill.
Skills compose with the brand profile. The profile decides voice and boundaries; the skill decides structure and steps for one kind of output. Keep each skill narrow — one job, one shape. A skill that tries to cover every report produces output that fits none of them.
The second time you retype an instruction is the signal it should be a skill.
Sections, order, length, what to do when a number is not measured.
Check the shape on real workspace data before you attach it to a cadence.
Once the shape holds, a recurring run produces comparable output week over week.
## Guardrails
Guardrails are your rules for everything the agents write in your name. Every draft, whether an article, a page fix, a social post, an outreach email or a community reply, is checked against them before anyone can approve it. The agents do the checking and the rewriting. You write a rule once, and every draft after it is held to it.
**Customize → Guardrails** has three sections, hardest first:
| Section | What a rule there does to a draft that breaks it |
| - | - |
| **Compliance** | What may never be said in your name. A draft that clearly breaks one is **stopped**. One that may break it goes to **review**. |
| **How agents write** | Your voice rules. A voice rule never stops a draft. One that may not follow it goes to **review**. |
| **Words to use and avoid** | Words and phrases a draft may never contain, as chips on a rule. Any one of them **stops** a draft, wherever it appears. |
Words to avoid are matched exactly, ignoring case, and on whole words: avoiding "cheap" does not stop "cheaper", and avoiding "HIPAA certified" stops "hipaa certified". They are listed explicitly, never guessed from a rule's text, so a sentence like *say "practice", not "clinic"* never stops a draft for saying "practice". Write that sentence as the rule, and put "clinic" in its avoid chips.
### When and where a rule applies
Every rule shows **When: … · Where: …** under its text.
* **When** is the rule's one-line description: the situation it is about.
* **Where** is the kinds of draft it holds: pages, articles, emails, community replies, social posts or ads. A rule applies to every kind unless you narrow it. A rule about email sign-offs, set to emails only, never holds back a page fix.
Only rules that are switched on are checked. Switching one off keeps it, so you can switch it back on later. Editing a rule is recorded as a new version, and every rule a verdict names carries the exact version the draft was checked against.
### The verdict on each draft
Each draft gets one verdict, for the exact text it was checked against:
| Verdict | What it means | What happens next |
| - | - | - |
| **Passed** | Every rule that applies was checked, and none is broken. | It waits for approval as it would have before. The check never removes a step. |
| **Review** | A rule may be broken. | Read it before you approve it, or ask for a revision. |
| **Stopped** | A compliance rule is broken, or the draft uses a word a rule forbids. | It cannot be approved as written, by anyone. Revise it first. |
| **Not checked** | The check could not finish. | It cannot be approved, published or sent until a check finishes. |
The verdict also counts the rules the draft was checked against: your rules that apply to it plus our own house safety rules, which every draft is checked against on every plan. A review or stopped verdict names each rule involved, and for a forbidden word, the word.
When the agent writes a draft, it is told the verdict straight away and revises once if it can keep the rule without losing the point. An article that is stopped is not filed for approval at all; if the revision is stopped too, the agent asks you in your [Inbox](/agent/inbox) what the article should say instead.
### A draft nobody could check is never passed
If the check cannot finish, because your rules could not be read or the check did not answer for every rule, the draft is **not checked**, never passed. It cannot be approved, published or sent until a check finishes. One row in your Inbox, **We could not check N drafts against your rules**, says so for the whole workspace, with a **Retry** button that opens the draft. A draft whose text changed after it was checked is checked again before it can be approved or sent. One that changed after it was approved is not published: it has to be checked and approved again.
A stopped or unchecked draft never goes ahead on its own, whatever the workspace's [approval mode](/agent/approval). A draft marked for review does not go out on the workspace's own say-so either: it waits for your approval instead.
### Unblocking a draft
There is no override button. A stopped draft moves only when it no longer breaks a rule:
* **Revise the draft.** Edit it yourself, or ask the agent to rewrite it. An edited draft is checked again as soon as it is saved, and a clean one can be approved.
* **Change the rule.** If the rule is wrong, edit it, narrow where it applies, remove the word from its avoid chips, or switch it off. Every draft checked under the old rules is checked again when you approve it.
* **Retry a draft that was not checked.** **Retry** on the Inbox row opens the draft. Approve it and the check runs once more before the approval goes through.
Your assistant gets the same answer. Approving a stopped draft over [MCP](/mcp/overview) or the REST API is refused with `409 Conflict` and a message naming the rules it breaks. Over the REST API the error's `details.reason` is `guardrail_stopped`, or `guardrail_unchecked` when the draft could not be checked, and `details.guardrails` is the whole verdict. A work item read over the API carries the same verdict as its `guardrails` field: `status` is `passed`, `review`, `stopped` or `unchecked`, and `checked` is how many rules it was checked against.
### On every plan
Writing rules is part of Knowledge and Skills. Checking drafts against them is not: every rule you have switched on, and our house rules, are checked on every plan. A workspace that moves to a plan without custom skills keeps the rules it wrote, and every draft is still held to them.
## What it will not do
Knowledge and Skills cannot make the agent assert something the data does not support. A brand profile that says "we lead the category" does not turn an unmeasured cell into a number, and a skill that asks for a share-of-voice ranking cannot produce one — Attensira measures mention and citation rates against a shared denominator, so brand rates do not sum to 1 and there is no brand rank metric to report.
It also does not change sampling. Depth stays at n=1 on Starter and n=3 on Growth, Business and Agency, and each Prompt × Model × country is still measured at most once per workspace-local calendar day.
If a skill asks for a metric that does not exist, you get an honest gap in the output rather than an invented figure. Write skills around Share of voice, Citation rate, Source share, and average citation position.
## Knowledge you write vs. what the agent already knows
The brand profile and skills on this page are yours to write. Separately, and on every plan, the agent keeps its own nightly notes about your workspace — see [What the agent remembers](/agent/what-we-know).
## On Starter
On Starter you have the business profile, and the agent reads and follows it and the rules in every chat and automation. Every draft is still checked against your guardrails and our house rules. Writing custom skills shows an upgrade state — a screen that names the plan requirement and does nothing else. Nothing you configure elsewhere is lost by being on Starter.
Plans are Starter $129, Growth $349, Business $699 and Agency $2,000 per month, with yearly billed at ten months' price. Crawler logs are also Growth-and-above. See [Plans](/account/plans).
## House skills
Besides the skills you write, the agent follows Attensira's own procedures for recurring jobs, such as `orchestrate/daily-win-plan` for the [Daily win plan](/agent/autopilot) and `report/client-weekly`, `report/client-monthly` and `report/client-quarterly` for [client reports](/agencies/client-reports). See [The agent's tools](/agent/agent-tools#house-skills).
# Loop health
Source: https://docs.attensira.com/agent/loop-health
Whether the autopilot loop is working, idle or blocked, and why: readings, automation runs and skips, findings, work produced and re-reads due, per day.
## What it answers
"Why has nothing happened?" Loop health answers that from records rather than guesses. For each workspace and each day it shows:
* **Readings per engine**: how many succeeded and how many failed.
* **Scheduled automation outcomes**: each automation that fired, was skipped (with the reason), or failed.
* **Detection findings** raised by the hourly [detection](/agent/findings) pass. A finding [held as noise](/agent/findings#noise-or-a-real-change) is not counted.
* **Work produced**: items filed for your approval or review.
* **Approvals pending**: how many items wait on a person.
* **Pull requests opened**.
* **Re-reads due**: [before-and-after](/measure/before-and-after) re-reads scheduled for that day.
Each count is read once for its date. The days are listed side by side, but they are not a trend and nothing in the report compares one day with another.
In the dashboard, [Home](/start/today#home) shows it only when the loop is **Blocked**: one status line with the first reason and **Fix it**, which opens the Inbox. The full report is `get_loop_health` over [MCP](/mcp/tools#get_loop_health) and `GET /v1/loop-health` over the [REST API](/api/endpoints#get-v1loop-health).
## The three statuses
| Status | Meaning |
| - | - |
| **Working** | Work was produced, or a pull request opened, in the last two days |
| **Idle** | Nothing is blocked, and nothing was produced. A quiet loop, not a broken one |
| **Blocked** | Something stops the loop. The reasons say which |
A workspace is **blocked** when any of these is true:
* the credit balance is spent;
* no reading succeeded in two days;
* nothing is scheduled and detection is off;
* every scheduled run failed;
* the scheduler stopped.
Every status comes with its reasons as sentences you can act on, such as "The credit balance is spent, so scheduled automation runs are refused. Top up to restart the loop." or "'Daily win plan' is paused." Reasons also carry notes that do not block the loop, such as how many pieces of work wait for your approval.
The org home at [/clients](/agencies/clients) currently shows a simpler status per workspace, derived from its automations: **blocked** when there is no scheduled automation, all of them are paused, or credits are used up; **idle** when an automation is scheduled but has never run, or has not run in 48 hours. It will read loop health instead once that is wired in.
## Why a scheduled run was skipped
A skipped run is recorded, grouped with a count, instead of disappearing. Each skip carries one of these reasons:
| Reason | What it means |
| - | - |
| `no_credits` | The credit balance was spent when the run was due. See [Credits](/account/credits#when-the-balance-is-spent) |
| `manual_trigger` | The automation only runs when started by hand (**When I ask**), so nothing was scheduled |
| `parked_on_ask` | The previous run is waiting on an answer from you; the scheduled fire handed that run back instead of starting another |
| `flag_disabled` | [Detection](/agent/findings) is off for this workspace, so a lost citation or a competitor change does not start work on its own |
| `retired` | The product paused the automation on its own (see `retired_reason` on the automation). Resume it to put it back on the schedule |
| `paused` | Somebody paused it |
| `cap_reached` | A [change cap](/agent/approval#change-caps) was reached; the changes over it wait for you to approve them by hand |
| `in_flight` | A run of the same automation was still in progress, so a second was not started |
| `not_worth_run` | Every event in the batch was a competitor's change that a [decision](/agent/decisions#worth-a-run) judged not worth a run of this event automation, so nothing started |
[Markets](/tracking/markets) add two lines of their own, named after the market or language rather than an automation. The first is recorded as a skipped run, the second as a failed one:
| Reason | What it means |
| - | - |
| `market_allowance_reached` | **Readings in DE** were skipped: this month's response allowance is held for your primary market, which keeps reading. They resume when the allowance resets |
| `translation_failed` | **Translations into de** could not be made, so readings in that market language wait and are retried on the next pass. They are never asked in the original language instead |
## Credits stop runs, not readings
A spent credit balance stops **scheduled automation runs**. It does not stop **readings**: the daily answers to your tracked prompts run on your plan's monthly response budget, not on credits. A blocked workspace with no credits still gets its numbers; what stops is the work built on them.
Loop health itself costs nothing and answers on a spent balance.
## Over the API
```bash Shell theme={null}
curl "https://api.attensira.com/v1/loop-health?days=7" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"status": "blocked",
"reasons": ["The credit balance is spent, so scheduled automation runs are refused. Top up to restart the loop. (Readings run on the plan's response budget, not credits.)"],
"days": [
{
"date": "2026-09-22",
"readings": { "chatgpt": { "ok": 40, "failed": 1 }, "perplexity": { "ok": 41, "failed": 0 } },
"runs": [
{ "automation_id": "aut_71cd", "name": "Daily win plan", "status": "skipped", "reason": "no_credits", "count": 1, "at": "2026-09-22T06:00:00Z" }
],
"findings": 2,
"work_produced": 0,
"approvals_pending": 3,
"prs_opened": 0,
"rereads_due": 1
}
]
}
```
`days` takes 1 to 30 and defaults to 7. The dashboard reads the same data from `GET /v2/projects/{id}/loop-health`.
# The morning brief
Source: https://docs.attensira.com/agent/morning-brief
One email a day at 07:00 in your own time zone, and on Mondays a longer note: what shipped, the early signals on the pages it changed, citations, goals, and what the agents do next.
## When it arrives
The brief sends at **07:00 in your workspace's own time zone** — not ours, and not UTC. A customer in Auckland and a customer in Denver both get a brief that says "this morning" and means it.
Six mornings a week it is the short daily edition. On **Monday**, again in your own time zone, it is the [Monday note](#the-monday-note): the week's work and what is showing up from it.
## The daily edition, in order
The newest finished piece of work since the last brief: a page rewritten, a pull request opened, an outreach draft filed. If there is nothing to lead with, this section is absent — the brief does not manufacture a highlight.
Up to **three** decisions waiting on you: the Inbox's top three, in the same order. The same problem is never listed twice. This is the same list as [Inbox → Waiting on you](/agent/inbox#at-most-three-decisions-first); the brief is a pointer to it, not a second copy of it.
Counts, with the sample size behind each one — never a percentage on its own, and never a day-over-day comparison. One day is too small a window to compare honestly; see [How we collect data](/measure/how-we-collect-data) for why.
Shown when [re-reads](/measure/before-and-after) are scheduled: at most **five** prompts, soonest first. The date shown is the day the re-read is actually read, a few days after its +7, +14 or +30 day mark, once its window has fully happened.
Shown when a change on one of your pages was first seen, or had its before-and-after decided, the day before (the week before, on Monday): at most **three**, moved first. Each is the [page change](/measure/page-changes) card's own two sentences, what changed and what was measured, never joined by a "because". These lines alone never send a brief on an otherwise quiet day.
The footer reads **"no proven change yet"** until a re-read has come back **moved**. That line is not boilerplate: the footer and any movement wording unlock only after a re-read clears the bar, the same significance test every delta in the product has to pass.
The daily edition is email only. It is never posted to Slack, whatever the Monday note's settings say.
## Quiet days send nothing
If there is no artifact, nothing needs you, and nothing has changed, no daily email goes out. A quiet day is not a failure to report on; it is the absence of anything worth interrupting your morning for.
## The Monday note
The Monday note answers a different question from the daily brief: what did the agents do for you this week, and is any of it showing up yet? It covers the seven days before Monday in your time zone, and every number in it carries its own dates.
The subject line leads with the work: **"4 shipped this week · OpenAI's crawlers fetched /vs/dentrix 41 times"**, or **"Nothing shipped this week · …"** when nothing did.
### What it says, in order
Shipped work and the early signals come first, because they are what can honestly be shown in a workspace's first weeks. Citations and goal pace take longer, so they come after.
One line on your goals: how many are ahead of pace, on pace, behind, or not measured yet. A workspace with no goals reads "No goals are set for this workspace yet."
How many pieces of work were delivered this week, by kind ("We shipped 4 pieces of work this week: 2 page changes, 1 pitch and 1 pull request."), then the newest **three**, each with its link and, for work that went live on a page, the day it did. Each piece of work is counted once, however many steps it took.
A week with nothing delivered says **"Nothing shipped this week."** in those words, followed by how many items are waiting on you when any are. If the delivery record could not be read that morning, the section is left out rather than printed as a zero.
The pages that delivered work changed and that went live in the last seven days. An open pull request, or a rewrite nobody has published yet, is not a changed page.
For up to **three** of them, most fetched first, how often AI crawlers fetched the page after it went live: "OpenAI's crawlers fetched /vs/dentrix 41 times between 24 and 28 September. The page went live on 24 September." Only verified crawlers count; a request that only claims to be one is left out. The two facts sit side by side, and the note never says the change caused the fetches.
Then Search Console's impressions and clicks on up to **ten** of those pages, with the days they cover and a note that Search Console trails today by two days.
When something cannot be counted, the note says why instead of printing a zero: the [crawler snippet](/traffic/ai-traffic) is not installed, crawler data could not be read that morning, Search Console is not connected, Search Console did not answer that morning or has not been asked about the changed pages yet, or the pages went live inside its two-day lag. A week in which no changed page went live says so, because there is then no page to count anything for. If neither source could be read at all, the section says "Crawler and Search Console data was unavailable this morning." and the note still sends.
Up to **three** changes the crawl saw on your own pages during the week, each with what it matched and what was measured. Shown only when there are any.
How many answers named or linked you for the first time this week, with up to **three** of them: the engine, the day, and the prompt it was answering. Then the [re-reads](/measure/before-and-after) that came back this week, as counts with their n: in how many prompt and engine pairs the difference cleared the sampling noise.
Each goal's pace, in the goal's own words when they pass the same honesty rules as the rest of the note, and in plain words ("on pace", "behind pace") when they do not.
While your workspace is in its first eight weeks, and always in its first Monday note, the note says which week you are in and what to expect. See [What to expect, week by week](#what-to-expect-week-by-week).
Up to **three** pieces of work you approved that the agents have not shipped yet, oldest approval first. "No approved work is waiting to start." when there is none. Then what we read again, and when.
Up to **three** things waiting on you, the same list as the daily edition.
One observation, stated as a count with its sample size — never a bare percentage. One verbatim answer quote from a real model response, word for word, never paraphrased.
The note ends with a link to that Monday's note in the app, and the same footer as the daily edition.
A section whose data source is not available on your workspace is left out. A section whose source was read and found nothing says so in words.
### What to expect, week by week
The note, the app when you sign up, and this page show the same four steps:
| Week | Signal | What happens |
| - | - | - |
| 1 | Shipped work | The agents ship the first changes, and this note lists each one with a link. |
| 2 | Crawler fetches | We count how often AI crawlers fetch each changed page after it goes live. |
| 4 | Impressions | Search Console starts to report impressions for the changed pages, about two days behind. |
| 8 | Citations and goals | We report whether answers name or link you for the first time, and how each goal is pacing. |
The steps describe what we measure and roughly when it can first appear. They are not a promise about what the engines do with the work: we guarantee the work itself.
### It always sends
The Monday note sends even on a quiet week. A week we found nothing more than "Nothing shipped this week" is itself worth saying out loud; suppressing it would make Attensira look broken instead of quiet.
## Getting the Monday note in Slack
The Monday note can go to email, to a Slack channel, or to both. Email is the default.
1. Connect Slack under **Customize → Connectors**.
2. Open **Settings → Notifications** and, under **Monday note**, pick the **Slack channel** to post in. It is one channel, shared with the win message under **Wins**.
3. Turn on **Slack**.
Slack cannot be turned on until Slack is connected and a channel is picked; settings refuse it rather than save a choice that would fail on Monday. At least one of email or Slack stays on: "nowhere" is what unsubscribing means, so to stop the note, set it back to email and [unsubscribe](#turning-it-off).
The Slack post is the same note as the email, posted by Attensira through your workspace's Slack connection. The agents themselves cannot post in your Slack.
If a post fails because of something on your side — the Slack connection was removed or needs reconnecting, the channel was archived, or the Attensira app was removed from it — you get **one** item in your Inbox: **"Reconnect Slack to keep getting the Monday note."** A post that fails for any other reason is tried again, and raises the same item only if the last try fails too. However often it happens, it stays one item, and it closes on its own once you reconnect Slack or the next post goes through. The email copy is separate and still goes.
## The same brief, wherever you read it
The email, the Slack post, the app, and your own tooling all get the same brief, composed from the same data each time rather than separate renderings that can drift apart. Pull it yourself with the `get_brief` [MCP tool](/mcp/tools#get_brief), or over REST with `GET /v1/brief`. Both take an optional `date` (`YYYY-MM-DD`) for one of your workspace's past days.
On a Monday the response also carries `order`: the note's section keys in the order it prints them.
A past Monday's note shows the crawler and Search Console numbers stored when it was sent that morning, not this week's. If none were stored that morning, the early-signals section is left out rather than filled with this week's numbers.
## Who gets it
Paid workspaces are enrolled in the morning brief automatically, the first time their subscription is active.
## Turning it off
The morning brief is its own [notification category](/account/notifications) and unsubscribes independently of Attention, Runs, Credits and the rest — turning it off does not touch anything else you get emailed about.
Unsubscribing stops the email, daily and Monday. It does not stop a Monday note you chose to post to Slack; turn Slack off under **Settings → Notifications → Monday note** for that.
# Notion blog
Source: https://docs.attensira.com/agent/notion
Connect a Notion database as your blog: approved articles arrive as draft rows, updates to existing posts wait for your yes, and nothing is ever deleted.
If your blog lives in a Notion database, Attensira can deliver articles into it and propose updates to posts that are already there.
## Connecting
Connect Notion from the workspace's integrations and grant access to the page that holds your blog database.
Choose the database your posts live in.
Tell Attensira which property holds what:
| Field | Required | What to map |
| - | - | - |
| Title | Yes | The title property |
| Slug | No | A text property holding the post's slug |
| Status | No | A status, select or checkbox property, plus its **draft** value and its **published** value |
| Body | No | Empty means the page content. A text property is limited to 2,000 characters |
| Published date | No | A date property |
The mapping is checked against the database's columns when you save it, so a property that does not exist or has the wrong type is refused then, not at the first delivery.
## How an article is delivered
When the agent writes an article that targets Notion, it waits in [Work](/agent/inbox#written-for-you) for your approval like every article does. Approving it (**Send to Notion**) creates a **new row** in the database, in its draft state.
* **Ask or draft mode:** the row stays a draft for your team to publish in Notion.
* **Act mode:** the row is set to published, and its published date to today, **only if** the mapping has a published value. Without one it stays a draft.
A post whose title already exists in the database is not duplicated. The item comes back to you with a link to the existing post.
## How updates work
The agent can list and read the posts in your mapped blog, and propose an update to one as a before/after in your [Inbox](/agent/inbox).
* Updates **always wait for your approval**, in every mode, act included.
* Approving replaces that post's content in Notion. The old blocks are archived, not deleted, so Notion's own history still has them. This is the one exception to the [Connectors](/agent/plugins) rule that nothing of yours is archived, and it happens only on your yes.
* An update never changes the post's status.
* We never delete a Notion post.
The agent tools behind this, `list_blog_posts`, `read_blog_post` and `propose_blog_update`, are offered only when a Notion blog database is mapped. See [The agent's tools](/agent/agent-tools).
## After it ships
Publishing through Notion starts the [before-and-after re-reads](/measure/before-and-after) for the tracked prompts attached to the article, the same as a merged pull request.
## Settings
The mapping is stored in project settings as `notion_blog`, with `database_id`, `database_title`, `title_property`, `slug_property`, `status_property`, `status_kind`, `draft_value`, `published_value`, `body_property` and `published_date_property`. Changes show in the settings log as `notion blog: X → Y`.
# Off-site work
Source: https://docs.attensira.com/agent/off-site
Most of what decides an AI answer is not on your site. Here is what the agent does about the pages, lists and repositories that are — pitches, entries in other people's repositories, and original pages worth citing.
## Why off-site at all
When an answer engine names a product, it is usually quoting somebody else's page — a roundup, a comparison, a README, a docs page. Fixing your own site matters and is table stakes. The rest of the work is on the sources that decide the answer, and none of those are yours.
The agent works those sources in one order, and it never skips a rung to reach a lower one:
A source an engine **already cites** for your questions, that names a competitor and not you. The engine has decided it trusts this page; the only missing thing is you on it.
A source that outranks where you are today but has not been read citing you yet.
Nothing worth joining exists. Then the move is to publish the page that would be cited — see [Original pages](#original-pages-worth-citing) below.
## The rules that bind every off-site channel
These hold on every channel — email, directories, repositories — and they are enforced in code, not by instruction.
* **Nothing is ever sent from an Attensira domain on your behalf.** Mail goes from your own connected Gmail or Outlook, or it does not go.
* **The first sends on any channel are yours.** Until ten pitches have gone out from your mailbox by hand, no pitch sends on its own; until you have approved one directory listing, no listing is filed on its own. Approving one channel does not unlock another, and one workspace never unlocks another.
* **A daily cap per channel, per workspace.** Two pitches a day, three directory listings a day. It applies to you as well as to automations, and rolls over at midnight UTC.
* **A listing always waits for you.** A directory listing publishes claims about your product under your name, and claims are never published unattended in any mode, including act mode.
* **Community posting is never unattended.** Reddit, forums and Q\&A answers are drafted for you to post, always. See [Community answers](/agent/community-answers).
* **Replies land in your mailbox**, because that is where the conversation is. We never answer one for you.
## Directory listings
When the deciding-sources map shows that a directory decided answers to your tracked questions, Attensira prefills that site's submission form from your brand profile — name, tagline, category, pricing, logo, website — attaches the citation counts that make the case, and puts it in your Inbox. It knows the forms for G2, Capterra, GetApp, Product Hunt, AlternativeTo, Clutch and Slashdot.
Anything the brand profile cannot answer is left blank and named as blank. We do not guess a logo URL or invent a category. Fill those in on the Knowledge page and the next listing carries them.
On your approval, we submit through a connected integration where one exists. Where none does — which is every directory today — you file it and mark it sent, and we take it from there.
## How a placement is verified
A placement is **verified** only when both of these are true:
1. **The page shows you.** We fetch the page and look for your brand name in its text or a link to your domain. We record which one we found.
2. **An answer cited it.** The page's URL turns up in a reading's citations, on a day after we did the work.
If the page shows you and nothing has cited it yet, the placement is **placed** — dated, honest, and re-checked daily for sixty days. It becomes verified when, and only when, an answer cites it.
If the page does not show you, nothing changes, however often an engine cites that page. An engine citing a page you do not appear on tells you the engine likes the page, not that the placement exists — a directory that quietly rejected a submission still gets cited every day.
The same rule applies to code: a merged pull request is not a deployed page, so we read the live URL before anything counts. Verification only moves forward. A page that returns an error today does not undo a placement confirmed yesterday. When a placement is verified, we schedule a re-read of your answers so we can tell you honestly whether it moved anything.
## Pitches
When a source an engine cites names a competitor and not you, the agent reads the page, finds who runs it from the page itself, and writes the pitch.
**What the pitch carries** is evidence, not adjectives: the verbatim answer passage that cited the page, the URL of the source, and the one page of yours that answers what its readers came for. The draft is under 150 words, makes one ask, and is signed by a real person from your profile.
**Where the draft goes.** It lands on an Inbox row you can read and edit. From there, press **Save as Gmail draft** or **Save as Outlook draft** and it appears in your own Drafts folder. You send it.
**Sending it for you.** With the workspace in **act** mode, and after the first ten have gone out by hand, and while there is room in the day's two, the agent can send the drafted pitch from your own connected mailbox. Anything else refuses and says which of those it was. Sending cannot be undone, so it is the one capability that is off unless you turn it on — see [Approval and modes](/agent/approval).
**What happens next.** Once a day the agent checks your mailbox for a reply to each pitch it sent. When one arrives, the row moves to **replied** and a line appears under **Waiting on you** in your [Inbox](/agent/inbox). It reads that a reply exists, who from and when — never the reply itself, and it never writes back.
**Who we never write to.** Anyone who has opted out, any address on a domain that has declined, and any market where prior consent is required and we do not have it. One email per site, one follow-up at most, never a third.
## Entries in other people's repositories
Some of the pages engines read are files in repositories: a curated `awesome-` list, another product's README, an ecosystem or integrations page in somebody's docs. A merged one-line entry there is a citation with no editor to persuade.
The agent reads the file, reads its `CONTRIBUTING.md`, checks the list's own stated criteria against your profile, and writes the single row in the file's own format. Then one of two things happens, and the Inbox row says plainly which:
* **Your connected GitHub account opens it.** If the account you connected has permission to open a pull request on that repository, the pull request is opened from your account, under your name.
* **You open it in one click.** Otherwise — and this is the usual case — the row carries the exact one-line diff plus a prefilled GitHub edit link. We are not installed on somebody else's repository and cannot open a pull request there for you, so we do not pretend to.
This is draft-only in every mode. **Act** is a standing permission over your own site; a stranger's repository is not that, and no standing yes covers it.
We change one line. We never reorder or reformat a file, never remove somebody else's row, never open a second pull request while one of ours is open there, and never chase a maintainer with an issue or a comment. Whether it merges is theirs to decide.
## Original pages worth citing
When nothing third-party is cited for a question at all, there is nothing to join. The move is to publish the page that would be cited — and to build it out of something nobody else has: what we have actually read for you.
Two shapes, and only two:
* **A data page**, answering a question with counts from your own readings — what four answer engines said about something, across a window, with the sample size.
* **A reference page**, defining and organising something the category argues about, with each definition attributable.
**Every claim carries its source** — a reading id from your own history, or a public URL read while writing it. A sentence with no source is deleted rather than softened, and a draft with fewer than three sourced claims is not written at all. The method comes before the numbers: what was asked, of which engines, over what window, how many samples. Every count carries its sample size in the sentence itself, not in a footnote.
**How it ships.** Through the same path as any other change: a pull request against your repository, with your documentation site treated as a first-class target rather than a fallback, because engines cite docs. With no repository connected, the full draft lands in your Inbox with the path it wants.
**How it gets found.** Once the page is live, it becomes the thing the pitches point at. Never before — a pitch pointing at a page that is not there yet costs you the contact.
## Community threads
Threads on Reddit, Hacker News, Stack Overflow and Stack Exchange, and Quora that AI answers cite are worked through [community answers](/agent/community-answers): on by default, drafted with a disclosure line, and never posted for you.
## Who has it
Paid workspaces are enrolled in the off-site work streams automatically, the first time their subscription is active.
## Where to see it
Every off-site act is on your [private receipt](/agent/what-the-agent-shows): what was drafted, what was sent, who replied, what was placed, and what a later reading verified. A placement counts as verified only when the page itself shows you **and** the URL later turns up in a reading's citations. A merged pull request is not a live page, and we check the live page.
# Connectors
Source: https://docs.attensira.com/agent/plugins
Connect the tools your work already lives in, see what each one lets the agents read and do, and reconnect one that stopped working.
Connectors give the agents reach beyond Attensira's own data. A connector is a third-party tool an agent may use on your behalf: your Search Console, your mailbox, your CMS, a Google Drive folder. You can also register **your own MCP server**, which makes every tool it exposes available inside a chat or an automation.
## Where to find them: Customize
Everything that shapes how the agents work for you is under **Customize** in the sidebar. It has five sections, one pill each. On every pill but Company, a search box filters the list you are looking at:
| Section | What it holds |
| - | - |
| **Connectors** | The tools the agents can read from and deliver to, your own MCP servers, and webhooks. This page. |
| **Skills** | Your playbooks, and the skills built into every agent. See [Knowledge and skills](/agent/knowledge-and-skills#skills). |
| **Guardrails** | Your compliance rules, how the agents write, and the words a draft may never use. See [Guardrails](/agent/knowledge-and-skills#guardrails). |
| **Agents** | The agents working in this workspace, by project. Read-only. |
| **Company** | The facts every draft is written from, your brand and values, and the competitors you track. See [Knowledge and skills](/agent/knowledge-and-skills#company). |
Customize opens on Connectors. The old **Plugins** address, `/plugins`, takes you there.
## Connecting a connector
Open **Customize → Connectors**, find the tool under **Available**, and press **Connect**. You sign in on the tool's own site; Attensira never sees your password. The connection is per workspace, and you can disconnect it at any time from the connector's **Manage** page.
Until a connector is connected, the agents cannot see its tools at all. Connecting is the only way a tool enters their toolset.
The list shows what you can connect today, grouped by what each connector is for. A tool we cannot reach, or one we have built but not shipped, is not on the page: a row you cannot use is not an integration, it is a roadmap. If the tool you need is missing, **Missing a tool? Request it** at the foot of the page reaches a person.
## Reads and Can
Every connector says, under its name, what connecting it lets an agent **read** and what it lets an agent **do**:
```text theme={null}
Reads your messages, threads and labels · Can draft emails and send them, each once you approve it
```
The two lines are written from the same rules that decide which of the tool's actions an agent may call, so they promise nothing those rules do not allow. Two things hold for every connector:
* **Can: nothing yet** means the connector is read-only. No agent can change anything in that tool.
* **Every write an agent makes waits for a yes.** An agent that calls a write stops and asks first, which is why each thing a connector can do says it happens once you approve it. Nothing is sent, posted or published on the strength of the connection alone. The one standing yes is [Act](/agent/approval#act): a workspace you switch to Act lets safe hygiene fixes go out without asking each time, and anything that makes a claim still asks.
Slack works differently: an agent cannot post to it at all. Attensira itself posts your Monday note and the wins you share, and only to the one channel you picked.
Reads run without asking: the worst a read can do is return something irrelevant. Nothing that deletes or revokes anything of yours is ever admitted, on any connector, and nothing is archived with one exception: an update you approve to a [Notion blog](/agent/notion#how-updates-work) post replaces the post's content, and the old blocks are archived, not deleted, so Notion's own history still has them.
| Connector | Reads | Can |
| - | - | - |
| Google Search Console | The queries, positions, impressions and clicks behind each page, your sitemaps and each page's index status | Nothing yet |
| Bing Webmaster Tools | Who links to you, how Bing crawls and indexes your pages, and the searches that bring people in | Nothing yet |
| PostHog | Your events, funnels and insights, and the pages people convert on | Nothing yet |
| Google Ads | Your campaigns, keywords and what each click costs, in the account you choose | Nothing yet |
| GitHub | The repository that builds your site, its metadata and its pull requests | Open pull requests with fixes, which merge only when you approve them |
| Notion | The pages and databases you share with it | Create pages, draft blog posts and publish them, each once you approve it |
| Webflow | Your sites, CMS collections and their items | Stage edits and new articles as drafts and publish them, each once you approve it |
| WordPress | Your posts and pages | Save a fix to a post as a draft and publish it, each once you approve it |
| Shopify | Your products, collections, blogs and shop details, never orders or customers | Rewrite product and collection descriptions and SEO text, and save new blog articles as unpublished drafts, each once you approve it |
| Google Drive | The files in the one folder you pick, never the rest of your Drive | Save finished work as a new file in that folder, once you approve it |
| Google Calendar | Your calendars, their events and your free time | Add an event to your own calendar with no guests invited, once you approve it |
| YouTube | Your channel, its videos and their stats, and their captions | Nothing yet |
| Canva | Your designs, templates and brand kit | Nothing yet |
| Gmail | Your messages, threads and labels | Draft emails and send them, each once you approve it |
| Outlook | Your messages, folders and threads | Draft emails and send them, each once you approve it |
| LinkedIn | Your profile, your company pages and how your posts performed | Share a post or an article, once you approve it |
| Slack | The names of your channels, so you can pick where the note goes | Post your Monday note and the wins you share to the one channel you pick, and nothing else |
| Reddit | Subreddits, threads and comments where your category is discussed | Nothing yet |
| Instagram | Your profile, your posts and how each one performed | Nothing yet |
| HubSpot | Your contacts, companies and deals, a page at a time | Nothing yet |
The page in your workspace is the authority: a connector appears there only when it can actually be connected right now.
### Google Drive works inside one folder
After you connect Google Drive, pick the one folder the agents work in. Until you do, every Drive call is refused, reads included, and the row sits under **Needs attention** with **Pick folder**. The default for "which of my files may an agent read" is none, not all.
With a folder picked:
* **Reads stay in that folder.** An agent can find a file or a folder, read a file's details, download a file, and export a Google Docs, Sheets or Slides file. Those named operations are the whole list. A search only looks directly inside the folder, and a file named by its id is checked to sit directly in the folder before it is read. Subfolders, sharing, revisions, comments and the rest of your Drive are out of reach.
* **Finished work lands in that folder.** The one thing an agent can do is save a new file there, after you approve it. It always lands in the folder you picked, whatever folder the agent names. It never overwrites, moves, copies, shares or deletes a file, and never changes a permission.
The folder you pick is checked against the connection: it has to be a real folder in the Google account you connected. Its name shows beside the connector under **Connected**.
### Google Calendar adds holds, and invites nobody
An agent can read your calendars, their events and your free time, so launches, reviews and calls are planned around time you actually have. The one thing it can do is add an event to **your own** calendar, once you approve it. Every guest is removed from the request and Google is told to send no updates, so nobody else is emailed or invited. It cannot edit or delete an existing event, or change a calendar's settings or sharing. Inviting people is outreach, and goes through the outreach path and its limits.
### Shopify reads products, never orders
An agent reads your products, collections, blogs and shop details. Orders, customers, payouts, discounts and themes are out of reach. What it can change is copy, each change once you approve it:
* a product's or collection's description, and its SEO title and description. Never its price, variants, title, handle or status;
* a new blog article, always saved unpublished. You publish it from your own Shopify admin.
It cannot delete a product or publish one.
## Needs attention, and reconnecting
Connectors that need you sit at the top of the page under **Needs attention**, each with the one button that clears it:
| Reason | What the row says | Button |
| - | - | - |
| The connection expired | Reconnect *Name*: the connection stopped working. | **Reconnect** |
| The connection is failing for another reason | Reconnect *Name*, and why. | **Reconnect** |
| No site or account chosen (Search Console, Bing, Google Ads) | Pick which site *Name* should read. | **Pick property** |
| No folder chosen (Google Drive) | Pick the folder *Name* should work in. | **Pick folder** |
| An Inbox row is blocked on this connector, or work waits on one that is not connected | The blocker, in its own words. | **Connect**, or **Open** |
When approved work is waiting on a connector, the row says how much: **3 items waiting**. Everything else that works is under **Connected**.
A connection is marked as needing a reconnect when the tool's provider confirms it no longer works: when an agent's call through it fails and the provider reports the connection expired, or when the daily check of every connection finds the same. A single failed call on a connection the provider still reports as working marks nothing. When it happens:
* one Inbox row, **Reconnect *Name***, is filed with a **Reconnect** button. It is not filed again for every failed call;
* work you already approved for that tool is not lost. It stays approved and waits, and it goes out after you reconnect;
* [loop health](/agent/loop-health) says how many connections need reconnecting.
Reconnecting is one sign-in. The row clears, the Inbox row resolves, and the waiting work resumes on the next publishing pass.
## Where finished work can land
Some connectors are places the agent can deliver to, not only read from. Each one writes under the same [approval mode](/agent/approval) as everything else the agent does outward, and each finished piece shows up in [Deliverables](/agent/deliverables) with where it lives.
| Connector | What lands there | How it goes live |
| - | - | - |
| [GitHub](/agent/github) | A pull request against the repository your page lives in | When the pull request merges and deploys |
| [Lovable](/agent/github#lovable) | A pull request on the repository your Lovable project syncs | When it merges, and you next publish from Lovable |
| [Webflow and WordPress](/agent/cms) | A change to one field of a page or post; in Webflow, also a new article saved as a draft | A field change when you approve it, or a hygiene change in act mode; an article when you publish it in Webflow |
| [Notion](/agent/notion) | A new row in your blog database, or an update to a post | As the [Notion page](/agent/notion) describes |
| [Shopify](/agent/shopify) | Product and collection copy, or a new blog article saved unpublished | Copy when it is approved, or a hygiene change in act mode; an article when you publish it from your Shopify admin |
Without one of these, the agent still does the work: it hands you the text to paste, and you mark it shipped once it is live.
## Registering your own MCP server
If you run an MCP server, register it under **Customize → Connectors** and the agent can call it like any built-in tool.
Give it a name and its URL. The name becomes the slug the agent uses to address it. If you already use the server in Claude Desktop or Cursor, paste that `mcpServers` configuration instead: each remote server in it is imported with its URL and its headers.
An open server needs nothing. Most servers that need a key expect it as a request header, such as `Authorization: Bearer …`, which comes across with a pasted configuration. Keys and header values are write-only: the page shows which headers are set, never their values.
Attensira calls `tools/list` on the server and registers what it finds.
A server that runs as a local program (a `command` entry in a pasted configuration) cannot be registered: there is nothing for us to reach over the network.
### How your tools are named
Discovered tools are namespaced:
```text theme={null}
mcp____
```
So a `search` tool on a server registered as `warehouse` becomes `mcp__warehouse__search`. The namespace makes collisions impossible: two servers can both expose `search`, and Attensira's own built-in tools can never be shadowed by a registered one, because the built-ins are assembled first.
### Servers that sign in with OAuth
You can register a server whose auth mode is OAuth, but the agent cannot call it yet: Attensira holds no OAuth token for it. The server appears to the agent as one tool that says it needs authorising, so the agent tells you that instead of failing with a 401 nobody can diagnose. Use `apiKey` or a header instead, or put an authenticating proxy in front of the server.
### When a server is unreachable
An unreachable server does not silently vanish from the agent's toolset. It becomes a single tool named `mcp____unavailable` that explains the problem when called.
That is deliberate. A tool that disappears looks to the agent like a capability that never existed, and it will confidently tell you it cannot do something it normally can. A tool that answers "this server is unreachable" gets you a real diagnosis.
### Limits
Discovery and calls are bounded so one slow server cannot hold up a whole session:
| Bound | Applies to |
| - | - |
| Discovery timeout | Listing tools when a server is added or a session starts |
| Call timeout | A single tool call |
| Tools per server | How many discovered tools are registered |
| Discovery budget | Total time spent discovering across all servers before a session starts |
A server that exceeds the discovery budget is skipped for that session rather than delaying the answer.
## How connectors relate to Attensira's own MCP server
These are opposite directions and are easy to confuse.
* **Connectors and registered MCP servers** are *inbound to the agents*: tools the Attensira agents can call.
* **[Attensira's MCP server](/mcp/overview)** is *outbound to you*: it lets your own assistant, such as Claude or ChatGPT, call Attensira. Set it up from **Settings → Your assistant**; see [Connect a client](/mcp/connect).
You can use either, or both. They share no configuration and no credentials: a connector's sign-in has nothing to do with your [Attensira API key](/account/api-keys).
## What connectors cannot do
* They cannot act without a yes: yours on the item, or the standing one you gave by switching the workspace to [Act](/agent/approval#act). Nothing that deletes anything of yours is admitted.
* They cannot be re-read on demand. There is no control that forces a connector to refresh, because there is no endpoint behind it. Rather than show a button that spins and does nothing, the product omits it.
* A connected tool is available to the whole workspace, not to one person or one chat.
# Projects
Source: https://docs.attensira.com/agent/projects
A project is a standing conversation with a crew we staff for one piece of ongoing work: what it shows, how you steer it, and what bounds it.
## What a project is
A **project** is a standing conversation with a crew of agents we staff for one piece of ongoing work, such as "win the comparison questions on Perplexity" or "keep the docs cited". You set the direction and answer the few questions only you can answer. The crew does the work and posts what it did, and why, to the project's feed.
A project has:
* a short name, shown as `#slug` (two to forty lowercase letters, digits or hyphens);
* a **focus**: what the crew works on, in a sentence or two;
* a **crew**: the roles staffed on it, and what wakes each one;
* **automations**: the crew's standing work, on a schedule or when something changes;
* optionally a **goal**, a **deadline** and a **monthly cap**.
A project lives inside a [workspace](/start/concepts#project-and-workspace). The workspace's prompts, competitors, connections and data are the project's too; a project narrows what the crew works on, not what it can read.
Ongoing work belongs in a project; a one-off question belongs in a [chat](/agent/chats). The conversations a project's crew has are listed under the project in the sidebar, not in Chats.
## Starting a project
Press **+** next to **Projects** in the sidebar and describe a problem or a goal in your own words. There is nothing else to fill in: no name, no team picker, no schedule.
The Chief of staff scopes it with you in a chat, then proposes the project as a card: a name, the focus, the crew and what wakes each member. Nothing is created until you press **Create project**. A chat that turns out to be ongoing work can end in the same card without you asking for it.
Creating the project:
1. makes the project and its crew;
2. creates one automation for each thing that wakes a crew member, marked **By Attensira** and yours to edit, test or switch off;
3. posts a kickoff line from the Chief of staff in the feed.
If a crew member needs an integration the workspace has not connected, it starts without that tool rather than failing, and the feed gets an **On your side** line such as "Connect GitHub so the Writer can open pull requests on your site. Until then they work without it."
If setup stops part way, the feed says so and an Inbox task, **Finish setting up #slug**, is raised. Creating the same proposal again picks up where it stopped; it never makes a second project or duplicate automations.
## The crew
You never define an agent. The Chief of staff staffs the project from a fixed set of roles, each with the tools and house procedures that role works with. To add someone, ask the Chief of staff in the project.
| Role | Wakes when |
| - | - |
| Chief of staff | You post in the project, and every week to report what the crew did and what moved. Always on the crew |
| Researcher | A competitor takes one of your questions, or a new source starts deciding the answers |
| Writer | A question you track has no page that answers it, or one of your pages stops being cited |
| Designer | A page AI answers already cite could travel further with a picture or a card |
| Web engineer | Something on your site stops AI crawlers or search engines from reading it |
| Community | A thread AI answers cite asks something you can answer |
| Analyst | Every week, to read what moved and whether it is more than noise |
| Outreach | A site AI answers cite could mention you, or a pitch is due a follow-up |
| Video | A video AI answers cite needs a better title, description or chapters |
| Ads strategist | You pay for clicks on a question AI answers already give away, or an ad contradicts your pricing |
| Copywriter | AI answers get your pricing, product or positioning wrong |
The project header's team menu shows what each member is doing now:
* **Working**, with what it is working on;
* **Waiting on you**, with the question or approval it is stopped on and where to answer it;
* **Stopped moving**, when a run has stalled. Open the run to see where;
* **Idle**, with when it wakes next.
When a change wakes one of the crew's automations, the run can be written up by the crew member it fits best: a competitor's new comparison page that wakes the Researcher's automation may be answered by the Writer. The run keeps that automation's tools, approval mode and caps; only the name on it changes.
## The feed
The feed is the project's conversation, oldest first. Each run has one line, which says **Working** while the run is under way and is updated in place when it finishes, so a run never floods the feed with a line per step. Lines you have not read are marked **New since you last looked**; that marker is yours alone.
| Line | What it is |
| - | - |
| Update | A message you posted, or a note from the Chief of staff such as the kickoff |
| Working | A run under way |
| Shipped | A run that produced something: an article, a pull request, a post |
| Result | A run that finished with an answer and nothing to ship |
| Needs you | A run stopped for your approval or your answer |
| Question | A question for you |
| On your side | Something only you can do, such as connecting an integration |
| Direction | A steer you gave |
| Plan updated | The crew changed how it works. See [Plan updated](#plan-updated) |
| Didn't finish | A run that failed or was stopped. The line says why in plain words |
A line that waits on you carries the same **Approve**, **Review** or **Answer** button as its row in the [Inbox](/agent/inbox), and every run's line has a **Replay** button that opens [that run, watched back](/agent/what-the-agent-shows#watching-a-run-back). Any line can open a thread.
### Because lines
Every run's line says why it started, in one sentence of at most 160 characters:
* *Because "Comparison check" runs every Monday at 09:00 BST*
* *Because competitor.com changed pages on its site*
* *Because AI answers stopped citing example.com/pricing, and 2 more changes*
* *Because you steered #comparisons: "Focus on multi-location practices"*
* *Because "Comparison check" was started as a test*
These sentences are built from what we recorded, the automation's schedule or the change we detected, never from a model's summary of it. A competitor's own words stay out of the sentence: they go to the run, not into a line we put our name to.
## Talking to the crew and steering it
The composer at the bottom of the feed has two modes.
* **Message** asks the crew something or hands it work. The Chief of staff answers in the feed and hands out the work.
* **Steer** sets direction: "Focus on multi-location practices, stop the SMB posts." The Chief of staff answers it the same way, and the steer is also kept, word for word, as the project's **direction**.
Every run of the crew's automations from then on is told the project's latest five steers, up to 2,000 characters together. The newest is always told, cut only if it alone is longer than that; older steers that do not fit are left out whole rather than cut. A project keeps its last 50. No model can write to the direction: it holds only what people typed.
A post holds at most 4,000 characters. An archived project takes no posts.
### Plan updated
A steer often means the crew's standing work should change: a different instruction, a different schedule, different tools. The Chief of staff can propose those changes to the project's automations, and what happens next depends on who wrote the automation.
* **By Attensira** (created when the project was set up): the change is made under the run's [approval mode](/agent/approval#projects-can-be-stricter), the stricter of the workspace's and the project's. In Ask it waits for your yes; otherwise it is applied. Every applied change posts a **Plan updated** line saying what changed: the instruction before and after, the schedule, the tools, or that the automation was paused. **Show the change** expands it.
* **By you**: nothing is changed. The Chief of staff shows the change as a card, and it is applied only when you confirm it.
Turning an automation back on is always your call, whoever wrote it. The Chief of staff cannot give an automation a tool that needs a stricter approval than anything it can reach today, and it cannot change the project's approval mode, its cap or its direction. Those are yours, in the project's settings and your own steers.
## The project panel
Beside the feed, the panel has three tabs:
* **Open work**: everything this project raised that waits on you or is moving. The same rows are in the [Inbox](/agent/inbox).
* **Deliverables**: what the project's runs shipped, with where each one lives.
* **Automations**: the crew's automations, each with its author (**By Attensira** or **By you**), whether it is on, and the first tools it may reach for. Each opens the automation's page, where it can be edited and [tested](/agent/automations#testing-an-automation).
Below 1280 pixels wide, the panel opens from a button in the header.
## Project settings
Open **Settings** in the project header.
| Setting | What it does |
| - | - |
| Name and focus | The project's name, and what the crew works on. Conversations in the project are told the current focus. The crew's automations were written with the focus the project was set up with, so when the focus changes, steer the project and the Chief of staff updates them |
| Goal | None, one of your measured goals, or a goal in words. A measured goal shows whether the project is on pace in the header; a goal in words has no number to pace, and the header says so |
| Deadline | Optional, and must be in the future when you set it |
| Monthly cap | In dollars. See [The monthly cap](#the-monthly-cap) |
| On its own | **Ask first**, **Leave drafts** or **Act**: how much the crew does without you. It can be stricter than the workspace, never looser. See [Approval modes](/agent/approval#projects-can-be-stricter) |
| Works on its own when… | One switch per automation, with its author. Turn one off and it stops running on its own until you turn it back on |
Changes are saved only when the server confirms them; a refused change says why and saves nothing.
## The monthly cap
A project can have a **monthly cap** of its own, in dollars, to the cent. Empty means no cap of its own: the workspace's limits apply. The cap is a budget for the crew's work in a month. It bounds what the crew spends, and it is not a promise of a result.
Only an org admin can change a project's cap, here or in billing settings; both edit the same cap. The settings show what the project has spent this month against it.
When the project reaches its cap, its new work stops until the month turns or the cap is raised:
* its scheduled and event runs do not start, and **Test** is refused;
* the Chief of staff does not start on new posts either. Your post is saved, the feed says the reply could not start, and a steer is still kept as direction;
* the feed gets one line for the month, such as "This project reached its monthly cap of \$400.00 for October, so its scheduled and event work is paused. It resumes on November 1, or as soon as the project's cap is raised in billing settings.";
* the Inbox gets one row, **Scheduled work is paused: this project reached its monthly cap**, naming the scheduled automations that did not run, with a **Raise cap** button. It closes itself once a run of that project is paid for again.
A run already under way always finishes, so a month can go over the cap by at most one run. Months are calendar months in UTC. See [Credits](/account/credits#project-monthly-cap) for how the project cap sits beside the workspace's.
## Paused and archived projects
A **paused** project's automations are off until it is resumed; anything you post to it still gets an answer. Resuming turns back on only the automations the pause turned off.
An **archived** project is hidden from the sidebar, its automations are off, and its feed, runs and slug are kept as they were. It takes no posts until it is resumed.
An automation whose project is paused or archived does not run, and a test of it is refused too. If we cannot read the project's state when a run is due, the run is refused rather than risk spending on a project you stopped. A scheduled or event run skipped for any of these shows in [loop health](/agent/loop-health), with the reason `work_area_inactive` (paused or archived), `work_area_unreadable` (its state could not be read) or `work_area_cap` (at its cap). A refused test says why when you press **Test**.
## Who sees a project
Everyone in the workspace sees every project in it. There are no per-project permissions. Unread counts and the **New since you last looked** marker are per person.
## Projects, automations and the API
The workspace's **Automations** page still lists every automation. Once the workspace has a project, a **Project** column says which project each belongs to, and a project's automation opens inside its project.
Over [MCP](/mcp/tools), these tools reach projects:
* `list_projects` lists the workspace's projects, leaving out archived ones: each one's `id`, `slug`, name, status, crew, goal, unread and needs-you counts, whether its crew is running, its `autonomy` and its `budget_cap_usd` (null when it has no cap of its own). With `id` it reads one project instead, adding its direction, its triggers, its `automation_ids` and the newest 50 lines of its feed, newest first. A read key is enough.
* `steer_project` takes a project's `id` and your `text`, at most 4,000 characters, and does what **Steer** does in the dashboard: the text is kept word for word as the project's direction, posted to its feed, and answered by a Chief of staff run, which is paid work. It changes no setting and approves nothing, whatever the text says. It needs a read-and-write key. Pass an `idempotency_key` so a retry posts nothing twice; the same key with different text is refused.
Creating a project, its settings, and pausing or archiving it are in the dashboard only. The [REST API](/api/endpoints#projects) has the same reads and the steer under `/v1/work-areas`: `GET /v1/work-areas`, `GET /v1/work-areas/{id}` and `POST /v1/work-areas/{id}/steer`. A project's runs and automations still appear in `list_sessions` and `list_automations` like any other. Where a payload refers to a project it says `work_area_id`, because `project` already means the workspace on every tool and endpoint.
# Searching what was actually said
Source: https://docs.attensira.com/agent/recall
Recall searches the answers we have already read for the passages nearest your question, and hands them back with the prompt, platform and day they came from.
## Counts answer "how often". Recall answers "in what words"
Most of what the agent reads for you is arithmetic: how often you were named, how often you were cited, which pages earned it. Those are the right answers to "how are we doing".
They are the wrong answer to "how is that competitor being *described*", or "what reason did the answer give for recommending them", or "is the claim we published being repeated back". Those questions are about wording, and the wording is in the answers themselves.
**Recall** searches those answers by meaning rather than by keyword. Ask it for "reasons given for choosing a cheaper tool" and it finds the passages that say that, whether or not any of them used the word "cheaper".
## Every passage comes with its receipt
A quote you cannot check is an anecdote. So every passage recall returns carries, on the same row:
| | |
| - | - |
| **Day** | The day the answer was read |
| **Platform** | Which AI platform said it |
| **Prompt** | The tracked question that produced it |
| **Passage** | The words, up to about a paragraph, taken from around your brand where the answer names it |
| **Reading** | The id of the sampled reading, so it can be opened |
The agent is told to quote these rather than paraphrase them. A summarised passage is a claim the platform did not make.
## What is searched, and what is not
Recall searches **the answers themselves, and nothing else.** Not the pages those answers cited, not snapshots of your site, not your own documents.
That is a deliberate limit rather than an unfinished one. Indexing every cited page alongside every answer would multiply the store roughly tenfold, for passages that are one sentence of somebody else's page — and that page is better read directly, which the agent can also do.
Answers are searchable from the point the feature was switched on. Older readings are indexed in the background, oldest first, so a long history fills in over the following days rather than all at once.
## Scope and window
Recall only ever searches **your own workspace**. There is no way to widen it — not from a question, not from an automation's settings, not by asking the agent to.
It searches the **last 28 days** by default, and up to 90 on request. If nothing in the window matches, it says so. "Nothing in the last 28 days matches that" is a real answer, and a better one than a paragraph assembled from memory.
## What it costs
A recall search costs a fraction of a credit — it is one small lookup, not a model call, and it is priced accordingly. It appears on your usage log like any other metered step. Reading your counts and boards stays free.
How the agent draws what it finds, and why a gap is never a zero.
# Reports
Source: https://docs.attensira.com/agent/reports
Ask the agent for a report: the full client report or the one-page board report, for the last complete week, month or quarter, on a link that opens without an account.
A report says, for a closed period, what was done and what it changed. The agent writes the words; every number on it is read from your workspace for that period, so a report cannot claim more than the data shows.
## Two formats
| Format | Who it is for | What is on it |
| - | - | - |
| **Client** (the default) | A client, or anyone who wants the detail | Visibility per engine, the work shipped, before-and-after re-reads, deciding sources, competitors and next steps. See [Client reports](/agencies/client-reports). |
| **Board** | A board, a CEO, anyone with a minute | One page: your goals against their pace, what shipped, what was spent, and one line on visibility. |
Both cover the **last complete period**: the ISO week, the calendar month or the calendar quarter. A board report is usually monthly.
## Getting one
* **In a chat.** Ask for it, "the board report for last month", or pick **Report** from the `/` menu first. A Report chat may read your data and publish the report, and nothing else that acts. See [Modes](/agent/chats#modes).
* **On a schedule.** The weekly, monthly and quarterly report [automations](/agencies/client-reports#making-reports) publish the client format. Say "board report" in an automation's instruction to publish that format instead.
* **By hand.** **Make one now** under **Results → Client reports** publishes the client format for the period that ended last.
Board reports appear in the same list under **Results → Client reports**, beside their period and marked as board reports, for example **Monthly board**.
In a chat, the published report opens in the [work panel](/agent/what-the-agent-shows#the-work-panel) under **Deliverable**, drawn as the report itself, with its share link above it to copy. The agent's reply says what the report is for and what to look at first; it does not retype the report's numbers.
## The board report
The board report reads in this order:
1. **Summary.** The totals in a sentence (how many goals stand at each pace below; how many pieces of work shipped; what was spent), and the agent's highlights.
2. **Goals.** One row per goal that existed by the end of the period and is not archived: what it measures, where it stands now with its sample size, the target, the deadline, and its pace.
3. **What shipped.** Shipped, waiting and failed counts, and the shipped work by kind, for example "4 pull requests, 2 directory listings".
4. **Spend.** What the workspace spent in the period, in US dollars.
5. **Visibility.** One line: named in X of N answers across every engine, how many days in the period were read, and how many changes a re-read proved to move.
6. **Next period.** What comes next.
### Reading a goal's pace
| Pace | Meaning |
| - | - |
| **Ahead of pace** | Past where a straight line from the goal's starting value to its target says it should be by now, by more than the reading's noise band |
| **On pace** | Within the noise band either side of that line |
| **Behind pace** | Short of that line by more than the noise band |
| **Not measured yet** | No reading yet. Its current value shows as **—**, never as 0 |
| **Reached**, **Missed**, **Paused** | The goal was hit, its deadline passed without it, or it is paused. These replace the pace |
The pace is the goal's own stored verdict, quoted as it stood at the end of the period. A goal on a business signal, such as sessions from Google Analytics, is marked **(business signal)**: it is shown beside the work, not credited to it.
### What the board report does not claim
* A section whose data could not be read says so: "The goals could not be read for this report", "Spend could not be read for this report". It is never shown as zero or left out.
* The agent's words follow the same rules as every report: no movement words (increased, dropped, improved) unless a [re-read](/measure/before-and-after) proved the change, and counts written as "X of N answers" rather than percentages.
* Nothing on it promises an outcome. Attensira guarantees the volume of work, not visibility; the report shows the work and what was measured after it.
## Sharing
Every report, client or board, gets a signed link at `app.attensira.com/r/report/` that opens without an account, lasts **180 days** and prints to PDF. Rotating the workspace's receipt link revokes every link at once. The page carries your org's [branding](/agencies/branding).
A report published from a chat is not emailed. Email delivery, recipients and the reporting standard work as described in [Client reports](/agencies/client-reports#email-delivery).
Sections, schedules, recipients and email delivery in full.
The only place a report may say something moved.
# Shopify
Source: https://docs.attensira.com/agent/shopify
Connect your Shopify store and the agent rewrites product and collection copy and adds blog articles as unpublished drafts. Nothing reaches shoppers without your yes or a hygiene change your workspace allows.
If you sell on Shopify, the product and collection pages are where shoppers and AI assistants read about what you sell. Connect your store and the agent can fix the copy on those pages itself, and put new articles into your store's blog, instead of handing you text to paste.
## What we change
On a **product**, a **custom collection** or a **smart collection**, three fields and no others:
| Field | In your Shopify admin |
| - | - |
| Description | The product's or collection's description |
| SEO title | **Search engine listing → Page title** |
| SEO description | **Search engine listing → Meta description** |
And in your store's **blog**: new articles, always created unpublished.
That is the whole of it. We never change a price, a variant, a product title, a handle, an image or a theme, and never touch orders, customers or discounts. We never publish or unpublish anything, never delete anything of yours, and never empty out an SEO title or description. Image alt text and structured data are not changed through Shopify.
**What we read:** your products, collections, blogs and shop details. Never orders or customers.
## Which pages it covers
A change is proposed against a page on your storefront at `/products/` or `/collections/`. A language prefix (`/fr/products/…`) is read past, and a product reached through a collection (`/collections/sale/products/…`) counts as the product.
On your store's primary domain or its `myshopify.com` address, a handle is matched to the product or collection exactly. The same path on any other domain is treated as a likely match, and the agent reads the page before proposing a change to it.
## When a change goes live
The same [approval mode](/agent/approval) that governs every outward write decides:
* **Ask or Draft mode.** The proposed change waits in your [Inbox](/agent/inbox). Nothing is written to your store until you approve it.
* **You approve it.** We write the field, and shoppers see the new copy.
* **Act mode, hygiene only.** A change goes live without asking only when it is class **safe** (no price, number with a unit, promise or competitor's name), its page is inside the paths you let act mode change, and the workspace is still in act mode at the moment we write. Changes are classed by the same rules as a repository change. The path list starts at `/docs/**` and `/content/**`, which no product or collection page is under, so until you add `/products/**` or `/collections/**` every change to your store waits for your yes.
* **Act mode, anything else.** The change is saved as a **proposal on the product or collection**, and waits in your Inbox for your yes. Shoppers see nothing.
### Proposals in your Shopify admin
Shopify has no unpublished version of a product's description, so a proposal is kept beside the product instead: a metafield in the `attensira` namespace, one per field (`attensira.proposed_description_html`, `attensira.proposed_seo_title`, `attensira.proposed_seo_description`). You can see what is waiting from your own admin.
The item goes back to your Inbox, and its timeline says **A draft is waiting on your site: Synced to Shopify (draft)**, with the reason it was not put live. When you approve it, the live field is written and the proposal metafield is removed. We only ever remove our own `attensira` metafields; a metafield of yours is never deleted.
### If the copy changed since you approved
Immediately before writing, we read the field again. If it no longer holds what the proposal was made against, because someone on your team edited it in the meantime, we do not write over it.
## New articles
An article the agent wrote for your Shopify blog waits in your Inbox for your yes, as every article does. Approving it adds it to the blog **unpublished**, whatever the workspace's approval mode: publishing it is always yours, from your Shopify admin.
* **Which blog.** If the store has one blog, that one. With several, the one whose handle reads like a blog (`news`, `blog`, `blogs`, `journal`, `articles` or `stories`). If none of them does, or more than one does, or the store has no blog at all, nothing is written, and the item comes back to you saying why.
* **No twins.** Approving the same article twice leaves one article: if the unpublished article an earlier approval created is already in that blog, you get that one back. An article you have already published is yours, and is never reported as our draft.
* Once you have published it, **Mark shipped** in the Inbox with its URL. That books the [re-reads](/measure/before-and-after).
## After it ships
A product or collection change is live once it is written. From then, the tracked prompts attached to it are [re-read](/measure/before-and-after) at +7, +14 and +30 days, and the result appears on the item and in [Deliverables](/agent/deliverables) under **What it did**.
## Connecting
1. Open **Customize → Connectors** in your workspace.
2. Find **Shopify** and press **Connect**.
3. Sign in to your store and approve the connection. The sign-in happens at Shopify; we never see your password.
4. Check the path list under the workspace's [approval settings](/agent/approval). It decides which pages act mode may change on its own, and it starts at `/docs/**` and `/content/**`: add `/products/**` and `/collections/**` if you want act mode to put hygiene changes live on your store.
Disconnect from the same place at any time. Disconnecting does not remove proposals already saved in your admin.
# Tables, charts, the work panel and replay
Source: https://docs.attensira.com/agent/what-the-agent-shows
What the agent draws alongside its reply, why an unmeasured day is a gap and not a zero, how to copy, download or pin it, the work panel that holds what a run made, and how to watch a run back step by step.
## The agent draws, it does not retype
When you ask the agent something about your numbers, it reads them and then draws them: a headline figure, a table of the rows behind it, and a trend line where there is one. Its written reply sits above that and says what the numbers mean — what moved, what it implies, what to do next.
This split is deliberate. A model asked to restate a forty-row table in prose does it at length, and occasionally retypes a number wrong. Handing it the rows and drawing them separately means the figures you read came out of the measurement, not out of a sentence.
## What it can draw
The agent does not write its own markup. It names a component from a fixed set, and the app draws it:
| Component | What it is |
| - | - |
| Metric | One headline number with its movement |
| Table | Rows and columns, capped at the top ten |
| Chart | A trend line over the window, one line per model |
| List, quote, prose | Ordinary reading blocks |
| Code, diff, link list | Reference blocks |
Two things follow from this. The agent can only produce layouts that have been designed, so a generated answer is held to the same visual standard as the rest of the app. And an app that has not shipped a component yet falls back to a plain-text description of what it would have said, rather than breaking the conversation.
## Gaps are gaps, not zeros
A day nobody sampled shows as a **break in the line**, never as a drop to the floor.
This is the same rule the dashboard follows. A share of voice of `null` means the prompt was not read that day; a share of voice of `0` means it was read and you were not mentioned. Those are different facts, and drawing the first as the second would invent a collapse that did not happen. The caption under a chart says how many days in the window were not measured.
If a chart looks sparse, that is usually sampling depth rather than a bad week. See [Reading the numbers](/measure/reading-the-numbers).
Movements follow the same rule. A change inside the sampling noise is reported as "no change" rather than as a small number with an arrow.
## Copying and downloading
Every table and chart the agent draws carries **Copy** and **Download**.
* **Copy** puts the rows on your clipboard tab-separated, which pastes straight into Sheets or Excel and still reads correctly if you paste it into a message instead.
* **Download** saves a `.csv`. Charts save the rows behind the line, not a picture of it — a picture is not something you can pivot.
Only the table or chart is copied. The tool calls above it and the agent's reasoning are not part of what you are taking away.
## Pin to board
A chart or table drawn from your own data also carries **Pin to board**. Pick one of your boards, or let Attensira make one called **My board** if you have none. The pin lands on that board under **Analytics → Boards**.
What is pinned is the read that drew the view, never the numbers or a picture of them. The board reads it again each time it opens, so next month it shows next month's numbers.
* Only views drawn from a read of your data can be pinned. A draft, a card, or anything drawn by a tool that fetched the web or spent credits has nothing that could be read again, so it has no **Pin to board**.
* Only your own boards take pins. The boards Attensira builds in are not offered.
* A board holds up to **24** pins. A full board is listed but cannot be picked.
* If a pin fails, a message says so and the card stays as it was.
## Seeing what it did
While the agent works, each step appears as it happens — reading your data, searching the web, fetching a page, changing what you track — each with its own mark, so a lookup is distinguishable from a crawl at a glance.
Once the steps have finished they collapse into a single line, and the answer sits directly under the question. Click that line to open them again. Two exceptions never collapse: a step that failed, and a step waiting on your approval.
### Worked for
When a run ends, one quiet line under it says how long it worked: **Worked for 2m 14s**. It counts from the run's first step to its end, less any time it spent waiting for your answer to a question, and shows at most two units, the larger first. A run that took less than a second says **Worked for under a second**.
## The work panel
The work panel holds what a run made, beside the conversation about it. Open it with **Work** at the top of the chat, and close it with **Hide work**; **Replay** opens it on the Replay tab.
On a window at least 1,024 pixels wide the panel sits beside the chat, and opens on its own when the run made something to take away. On a narrower window it opens over the chat, only when you ask for it.
| Tab | What it holds |
| - | - |
| **Deliverable** | The thing to take away: an article, a page, an image, or a published [report](/agent/reports). Copy it, or press **Review and publish** to open its approval in the [Inbox](/agent/inbox). A report is drawn as the report itself, with its share link to copy. |
| **Changes** | Every piece of work the run filed, with where it stands: waiting for your yes, revised, approved, or already in your Inbox. Each opens its Inbox item. |
| **Browser** | The pages the agent read, with what it took from each. |
| **Replay** | Every step the run took, in order, with its time and the credits it was charged, and the run's totals. |
A tab appears only when it has something in it, so a run that only answered a question usually shows **Replay** alone.
Nothing is published from the panel. **Review and publish** goes to the work item's approval in the Inbox, where your [approval mode](/agent/approval) applies as it does everywhere else.
## Watching a run back
Any finished or running piece of agent work can be watched back step by step. The replay is the same wherever you open it:
* **Replay** on a line in a [project's feed](/agent/projects#the-feed), including a **Plan updated** line;
* **Replay** on a run in an [automation's history](/agent/automations#runs-and-run-history), and on a test run once it starts;
* a deliverable, and an item in the [Inbox](/agent/inbox), open the run that made them;
* in a chat, the **Replay** tab beside the conversation.
Each of these opens exactly the run it belongs to, never a neighbouring one.
The header says who did the work (the crew role, in a project), which project it was for, when it ran, and its **Because** line: why it started, in one sentence built from the schedule or the change we recorded, never from a model's summary.
Under it are the run's totals, how long it was working, the credits it spent and how many steps it took, and then the steps in the order they started. Each step says what was done and to what ("audited example.com/pricing", "asked for your approval"), how long it took, the credits it was charged (free reads show none), and how it ended. A step that failed shows the reason in plain words. A stop for you shows how it was settled: approved, declined, answered, withdrawn, never answered, or still waiting on you.
Beside a step is what it saw or made:
| Frame | What it shows |
| - | - |
| Page | A page the step read: its address, its title and an excerpt quoted word for word from what was fetched |
| Document | A document the run rewrote, as a before-and-after of the two versions |
| Data | A table or chart the step drew |
Runs do not take screenshots, so the screenshot slot of a page says **No screenshot for this step** rather than showing an empty box.
In the dialog, play steps through on their own, drag the scrubber, or use the keyboard: the arrow keys move a step, **Home** and **End** jump to the first and last, and **Space** plays or pauses.
A replay carries at most 500 steps. A longer run shows the first 500 and says so; its totals still count every step.
The replay is a summary of the run, not its raw log: a step shows its one-line description rather than everything the tool was called with, and a failure shows the plain-words reason rather than the internal error.
How to ask a good question, what the box can do, modes, and the questions the agent asks you.
# What the agent remembers
Source: https://docs.attensira.com/agent/what-we-know
The evidence-cited facts the agent derives about your workspace every night, so a conversation doesn't start by rediscovering what last night's run already knew.
## What this is
Every night, a job looks back over your workspace's readings and actions and writes down what it learned — in plain sentences, each one carrying the evidence it rests on. The next time you open a chat or an automation runs, the agent already knows the shape of your workspace instead of spending its first tool calls rediscovering it.
## What gets recorded
* **Top deciding domains per engine**, with their share and the sample size behind it — the same domains [Who decides an answer](/measure/deciding-sources) shows, folded into a standing fact instead of a live query.
* **The source mix** — how much of what decides your answers is your own pages, competitors, or everyone else.
* **Prompts won and prompts lost**, and which ones.
* **Differences by surface or by country** — but only when the difference clears the same significance test the rest of the product uses. A split that is not outside the noise is not written down at all; "consumer and API look the same" is the correct sentence at a small sample, and stating a difference that isn't real would have the agent repeat a lie for ninety days.
* **How often each kind of off-site action got placed** — pitches, listings, mentions you pursued — and how often they landed.
## Every observation carries its evidence
A sentence without a sample size is noise in a confident font. A sentence without evidence is a claim nobody can check — and once it is folded into every future conversation, an unchecked claim is indistinguishable from a hallucination that reinforces itself. Every observation states the `n` it stands on and points at what it was derived from.
## They expire
An observation is good for **90 days**. After that it ages out rather than sitting there as a stale fact the agent keeps repeating about a workspace that has since changed.
## The agent can write one too — with evidence that resolves
The agent is not limited to what the nightly job derives. It can record an observation itself, but only when the evidence behind it actually resolves against your workspace — a claim citing something that does not check out is rejected outright, not written down with an asterisk.
## Available on every plan
This is system memory, not a paid add-on. It ships on every plan — unlike writing custom skills in [Knowledge and Skills](/agent/knowledge-and-skills), which is Growth and above. What the product itself learned about your workspace is available regardless of plan.
## Shown to the agent as data, never as instructions
Observations are third-party in origin — nightly ones are derived from prose written by ChatGPT, Perplexity and Gemini, which in turn quotes pages nobody at Attensira controls, and agent-written ones come from a model. Nothing in an observation is ever treated as something the agent should *do*. It is read the same way any other untrusted context is read: fenced off and scrubbed before it reaches a conversation, so a stray instruction hidden in a quoted page cannot steer what the agent does next.
See [Reading the numbers](/measure/reading-the-numbers) for how `n` and significance work everywhere else in the product — observations use the same rules, not a separate standard.
# Wins: the first time an answer names you
Source: https://docs.attensira.com/agent/wins
What counts as a win, how the hourly pass decides it, the message you get, the share link, and the win_detected event.
## What a win is
A **win** is the first time an engine names your brand, or links to your own site, in an answer to a prompt it had been answering without you. It is recorded per prompt and per engine, so ChatGPT naming you on a prompt and Perplexity naming you on the same prompt are two wins.
There are two kinds, and each is its own first:
| Kind | What happened in the answer |
| - | - |
| **Named** | The answer named your brand in its own words |
| **Linked** | The answer cited a page on your domain, or on a subdomain of it |
An engine can name a brand it never links, and link a page while recommending somebody else, so one does not stand in for the other. Each (prompt, engine, kind) can be a win **once**, ever.
A win says what happened and when we started reading. It never says why. The message reads "for the first time since we started reading this prompt", not "after we shipped" or "because of our work".
## What counts
Every hour, a pass reads the last three days of your [readings](/measure/how-we-collect-data), in your workspace's time zone. It is arithmetic over the same numbers your dashboard shows, with no model involved, so the same readings always produce the same wins.
A naming is a win only when all of these are true:
A prompt that names you ("is Acme any good") is a question about you, and being named in its answer is not news. The prompt's text is checked against your brand's name, your domain and your domain's bare name, as whole words. A prompt that names a **competitor** ("Acme vs Curve") still counts: that is a question a buyer asks.
A prompt you deleted, or an engine you removed from a prompt, produces no wins.
Before the day you were named, at least **three** earlier days carried at least one successful reading of that prompt on that engine.
On no earlier day with a successful reading did the answer name you (for a named win) or link to you (for a linked win). Every earlier day is checked, not only the last three.
Answers for the same prompt from different surfaces or countries on the same day are one day here. The question is only whether you were in the answer.
### Failed reads never count
A day counts only if at least one reading of the prompt on that engine succeeded. A day of failed reads and a day with no reading at all are the same to this pass: silence. A failed day cannot be one of the three earlier days, and it cannot be the day you were named.
### What is never announced
* **Names from before detection started.** The first pass on a workspace records everything you are already named or linked for as a baseline and announces nothing. Turning detection on does not announce months-old news as a first.
* **A naming we could not call a first.** If an engine named you on a prompt before we had three successful earlier days of reading it, or had named you on it before, that (prompt, engine, kind) is recorded as a baseline. It is never announced and never becomes a win later.
Baselines are never listed, shared or counted as wins.
## The win message
When the pass finds new wins, you get **at most one message per workspace per day** in your workspace's time zone, listing every win not told yet. A win found after that day's message went out is in the next day's message.
Each win is told in one sentence:
> ChatGPT named Acme for the first time since we started reading this prompt on 2 September 2026, in an answer to 'best dental software' on 28 September 2026.
"Since we started reading" is the first day we have a successful reading of that prompt on that engine. A linked win says "linked to acme.com" instead of "named Acme".
Under the sentence:
* **The quote.** The engine's own words, verbatim, cut from the answer we read: at most 400 characters around the first mention of your brand (or the cited link, for a linked win). When the quote is cut from a longer answer, it is shown with "..." at both ends. A quote is never edited or summarised. If no answer that day yields a quote that contains the mention and passes our honesty check, the win has no quote rather than a tidied one.
* **Related work**, only when it is true. If a piece of work that lists this prompt went live in the 90 days before the win, the message adds "Work on this prompt: *title*, live since *date*." as a separate line. It does not claim the work caused the win, and work that is not live yet is never mentioned.
### Where it goes
The message goes by email by default. Under **Settings → Notifications → Wins** you can send it to Slack instead, or to both; Slack posts to the same channel as the Monday note. See [Notifications](/account/notifications).
The win email is its own notification category, so you can unsubscribe from it without turning off anything else.
## The win card in the dashboard
The newest win nobody in your workspace has seen yet appears as a card at the top of **Home**, and at the end of every project's feed. Most visits have none, and then nothing is drawn.
* **Shown once.** The card counts as seen as soon as it is on screen, so it does not come back on your next visit, on Home or in a project.
* **Share in one tap.** The card carries LinkedIn, Slack and **Copy link**. Slack posts the card to the workspace's Slack channel; see [Sharing a win card](#sharing-a-win-card) for what the link is.
* **Close** takes the card off the screen. The win stays in your list of wins, and its link, if it has one, keeps working. **Dismiss** puts the win away for good and, when it has a link, turns that link off in the same step. The button says **Dismiss and turn off the link** when there is one.
### The referral offer
After you share a win, and in the card's place when you close or dismiss it, you may see **Give a month, get a month**: your referral link to send to a team that should be in AI answers too. When they start a plan with it, you each get a month. It appears only after a win, and only when your organisation is eligible; **Not now** hides it for the rest of that visit. See [Referrals](/account/referrals) for who qualifies, the link and the rules.
## Sharing a win card
A win card shows the quote, the engine, the prompt and the date. Sharing it to LinkedIn or copying its link makes a **public link** to the card.
* **One win, one link.** The first share makes the link and every later share reuses it, so a LinkedIn post and a copied link open the same card. On LinkedIn, the link is opened in LinkedIn's own share box; Attensira does not post anything for you.
* **Permanent until you turn it off.** The link has no expiry of its own. It stops working when you **unshare** the win or **dismiss** it. Dismissing puts the win away and turns off its link in the same step.
* **A turned-off link stays off.** Sharing the win again makes a new link. The old one never works again.
* A dismissed win cannot be shared.
### What a stranger sees
The person who opens the link needs no account. The page shows only your brand, your domain, your logo (when your brand profile has one), the engine, the prompt, the quote and the date. It shows no ids, no counts, nothing else from your workspace, and no Attensira branding, so an agency can share a client's card as it is.
A link that was turned off, or was never a win card, shows "This card is not available".
## The `win_detected` event
Every win is also recorded as one `win_detected` event, with the win's id as its subject. Its payload carries:
| Field | What it is |
| - | - |
| `win_id` | The win |
| `prompt_id`, `prompt_text` | The prompt |
| `platform` | The engine |
| `kind` | `named` or `linked` |
| `slot_day` | The day of the answer that named you, in your workspace's time zone |
Today no automation you create reacts to it. `win_detected` is not one of the event types you can pick in the dashboard, over MCP or over REST, and each of those asks for at least one type, so a new [event automation](/agent/autopilot#event-automations) never listens for it. The built-in **Event responder** does not listen for it either. Only an automation saved with an empty event list would, because an empty list means every event type.
## Reading your wins
* **Over MCP**, the `list_wins` [tool](/mcp/tools) returns your wins newest first, each with the same sentence the message uses, the quote, the date, "since we started reading" and the share link when there is one.
* **In the [morning brief](/agent/morning-brief)**, the Monday edition counts the week's wins.
* **In the dashboard**, the newest unseen win is a card on Home and in project feeds. See [The win card in the dashboard](#the-win-card-in-the-dashboard).
Dismissed wins are left out of the list.
## Who has it
Wins run on every workspace with hourly [detection](/agent/findings#who-has-it). Paid workspaces are enrolled automatically, the first time their subscription is active.
If the pass fails for a workspace, the failure shows on [loop health](/agent/loop-health) as **First-naming detector**, and the next hour's pass tries again. If the day's message could not be queued on any channel you chose, its wins stay stored and untold, and the next hour's pass tries again; a win is never lost.
# Authenticating REST API requests
Source: https://docs.attensira.com/api/authentication
Send a workspace-scoped API key as a bearer token. The key names the workspace, so no request can be pointed at someone else's.
Every REST API request except `/healthz` needs a workspace-scoped API key, sent as a bearer token.
```bash Shell theme={null}
curl https://api.attensira.com/v1/analytics/visibility \
-H "Authorization: Bearer atn_live_"
```
It is the same key and the same header as the [MCP server](/mcp/connect). One key reaches both surfaces; you do not need a second credential to add the other.
The REST API takes a key and nothing else. OAuth access tokens belong to the [MCP server](/mcp/oauth), where a client can open a browser; an API called by a script should not need a consent screen, and a key is the one credential that works where nothing can open a browser at all.
## The key
Mint a key under **Settings → Your assistant** at [app.attensira.com/settings/access](https://app.attensira.com/settings/access). See [API keys](/account/api-keys) for minting, scopes and revocation.
The format is `atn_live_` followed by 32 hexadecimal characters:
```text Key format theme={null}
atn_live_0123456789abcdef0123456789abcdef
```
The key is displayed exactly once, because only a hash is stored. Attensira cannot re-display it — a lost key is replaced, not recovered.
REST keys do not expire. A key stays valid until you or a workspace admin revokes it, so a script you deploy once keeps running without anyone re-authorizing it. There is no rotation deadline and no maximum age.
Access tokens obtained through OAuth *do* expire, and the client refreshes them for you. That lifetime is a property of the token, not of this API — nothing about `/v1` times out a key.
## The credential names the organisation
A credential belongs to your organisation and names one workspace inside it as the default. Every `/v1` endpoint takes an optional `?project=`; omit it and the default is used.
No workspace id appears in a path segment or a body field — the query parameter is the only place one can be named, and it is checked against the organisation the credential belongs to. A workspace outside it comes back `404`, indistinguishable from one that does not exist, so naming an id you do not own tells you nothing.
One credential therefore covers every workspace in the organisation. `GET /v1/account` returns them in `workspaces`, alongside the resolved one in `project`.
## What a 401 means
A request with a missing, malformed or unusable key gets:
```json 401 response theme={null}
{
"error": {
"type": "unauthorized",
"message": "missing or malformed Authorization header. Send: Authorization: Bearer atn_live_<32 hex>",
"status": 401
}
}
```
The response carries the challenge, so a client knows which scheme to retry with:
```http theme={null}
WWW-Authenticate: Bearer error="invalid_token"
```
A `401` covers four different situations deliberately, and neither the message nor the challenge distinguishes them: the header was absent or the wrong shape, the credential does not exist, it was revoked, or it expired. The challenge is one constant string for all of them — a caller who could tell them apart could probe for which credentials exist. Check the key at [app.attensira.com/settings/access](https://app.attensira.com/settings/access) rather than inferring from the response.
A key that is real but was minted read-only gets `403`, not `401`, from an endpoint that writes: the error envelope carries `type: "forbidden"` and the header `WWW-Authenticate: Bearer error="insufficient_scope", scope="write"`. The distinction matters: a `401` is a reason to check the key, a `403` is not, and a client that reacts to one as if it were the other throws away a working key and fails identically. Keys cannot be widened after minting — mint a read-and-write key. `GET /v1/account` returns the credential's `scopes`, so that is where to look when a write fails and a read succeeds.
## How the token travels
The API service checks only the *shape* of the key at the door — the `atn_live_` prefix and 32 hex characters — and rejects anything else before it can become a round trip, so a typo never reaches the backend or its logs. That check is not authentication.
The token itself is forwarded verbatim to the Attensira backend, which owns the real validation: hash lookup, revocation, expiry, scopes and per-key rate limiting. Two consequences worth knowing:
* **Redirects are never followed.** A `3xx` is treated as the response rather than chased, because following one could re-send a live key to another host.
* **A key is never cached or mirrored.** Revoking a key at the dashboard takes effect on the next request; there is no copy of it in the API layer to go stale.
## Keeping a key safe
Send it from a server, never from a browser or a mobile app — anything shipped to a client device is public, and this key can delete prompts and spend credits.
Store it in your platform's secret store and read it from an environment variable. A key in a committed `.env`, a CI log or a screenshot should be revoked and replaced rather than reasoned about.
Mint one key per integration. Keys carry `last_used_at`, so a per-integration key is the difference between revoking one job and breaking every job you have.
# REST API endpoint reference
Source: https://docs.attensira.com/api/endpoints
Every Attensira REST endpoint — parameters, request bodies, response shapes, and which ones spend credits or delete data.
Every endpoint lives under `https://api.attensira.com/v1`. Every one takes `Authorization: Bearer atn_live_<32 hex>`, and every one also takes an optional `?project=` when the key's organisation has more than one workspace — see [authentication](/api/authentication).
Each endpoint below names the [MCP tool](/mcp/tools) it is the twin of, and answers with the same payload. The tools without a REST endpoint yet are listed under [MCP-only for now](#mcp-only-for-now) at the end of this page.
`?project=` is always the workspace, never a project. A [project](/agent/projects) is a **work area** on the wire: it is read and steered under `/v1/work-areas`, and a goal or a deliverable that points at one carries its id as `work_area_id`. Creating a project and changing its settings are in the dashboard only.
## Before you start: what costs money and what destroys data
| Endpoint | Effect | Why |
| - | - | - |
| `POST /v1/prompts` | Spends credits | Each new prompt is read across your tracked platforms from the next run onward |
| `POST /v1/automations/{id}/runs` | Spends credits | Triggers an automation immediately instead of waiting for its schedule |
| `POST /v1/ask` | Spends credits | Runs the agent on an agent model |
| `POST /v1/sessions/{id}/context` with `run: true` | Spends credits | Queues the session again to act on the new context |
| `POST /v1/inbox/tasks/{id}/answer` | Spends credits when the run that asked had finished | A finished, failed or suspended run starts a new turn on the answer |
| `POST /v1/work-areas/{id}/steer` | The reply spends credits | Posting is free; the project's crew answers the post with an ordinary agent run |
| `DELETE /v1/prompts/{prompt}` | Destructive | Stops tracking a prompt; its history goes with it |
| `DELETE /v1/competitors/{id}` | Destructive | Stops scoring a rival; past readings keep what they recorded |
| `DELETE /v1/automations/{id}` | Destructive | Removes the automation and its schedule. Past runs are kept as sessions |
| `DELETE /v1/sessions/{id}` | Destructive | Deletes a finished session and its trace |
Nothing else on this page produces a bill or erases history.
## Status codes
A `POST` that creates a durable resource answers `201`. `POST /v1/ask` and `POST /v1/automations/{id}/runs` answer `200`, because what they return is a handle to work in progress rather than a resource at a new URL. Everything else answers `200`. Failures are the [error envelope](/api/errors).
***
## Find and measure
### GET /v1/search
Read-only. No credits. MCP tool: `search`.
One search across prompts, pages, automations and sessions. Use it to locate something by wording when you do not have an id.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `q` | query | string | — | Required. Matched against prompt text, page URLs and titles, automation names and instructions, and session titles and transcripts |
| `limit` | query | integer | 20 | Maximum 50 |
| `offset` | query | integer | 0 | For paging |
```bash Shell theme={null}
curl -G https://api.attensira.com/v1/search \
--data-urlencode "q=pricing" --data-urlencode "limit=2" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"total": 37,
"has_more": true,
"hits": [
{
"type": "prompt",
"id": "prm_8f21",
"title": "best crm for small teams pricing",
"snippet": "…compared against three competitors on price…",
"when": "2026-08-30T09:12:00Z"
},
{ "type": "page", "id": "https://acme.com/pricing", "title": "https://acme.com/pricing", "snippet": "Pricing — Acme", "when": null }
]
}
```
`type` tells you which endpoint can fetch the hit in full: `prompt` → `GET /v1/prompts/{prompt}`, `page` → `GET /v1/pages`, `session` → `GET /v1/sessions/{id}`, `automation` → `GET /v1/automations/{id}`. A hit whose label starts with the query ranks above one that merely contains it; at equal footing prompts come before pages, then automations, then sessions. Answers themselves are not searched — they are read verbatim through [`GET /v1/memory/answers/history` and `GET /v1/memory/answers/slice`](#the-receipts).
**Every string on a hit is capped at 400 characters**, not just `snippet`. A complete model answer can never come back through search, no matter which fields a hit carries. Fetch the session for the full text.
Page on `has_more`. `total` is how many items matched up to the server's fetch ceiling — a floor, not a grand total.
### GET /v1/analytics/
Read-only. No credits. MCP tool: `get_analytics`.
The main measurement endpoint. One call returns an aggregated, ranked slice of your tracking data for a window.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `area` | path | enum | — | Required. See the table below |
| `days` | query | integer | 30 | 1–365 |
| `order` | query | enum | `worst_first` | Or `best_first`. Rows nothing was measured for sort last either way |
| `limit` | query | integer | 20 | Maximum 100 |
| `filter` | query | string | — | Case-insensitive substring on row labels. For `area=platform` it names the platform and is required |
| `area` | One row per | Answers |
| - | - | - |
| `visibility` | tracked platform | Share of voice per platform, with the workspace's own rate alongside — the orienting call |
| `prompt` | tracked prompt | Which prompts you win and lose |
| `page` | page of yours | Which of your pages get cited, as a rate on every answer sampled |
| `source` | external domain | Which domains the models cite in your category, as a share of every citation |
| `competitor` | rival | Workspace-wide share of voice per competitor, with the competitor id |
| `topic` | prompt topic | Per-topic rollup; prompts under no topic are one row with id `no_topic` |
| `platform` | the platform named in `filter` | That platform end to end |
| `bot` | AI system that fetched your site | Reads, pages reached, purpose mix, when it last came |
```bash Shell theme={null}
curl -G https://api.attensira.com/v1/analytics/prompt \
--data-urlencode "days=30" --data-urlencode "order=worst_first" --data-urlencode "limit=1" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"area": "prompt",
"days": 30,
"order": "worst_first",
"filter": "",
"total_rows": 12,
"truncated": true,
"visibility": { "value": 0.3, "n": 20 },
"rows": [
{
"id": "prm_8f21",
"label": "best crm for small teams",
"topic": "buying intent",
"country": "US",
"visibility": { "value": 0.12, "n": 25 },
"share_of_voice": { "value": 0.12, "n": 25 },
"citation": { "value": 0.04, "n": 25 },
"position": null,
"delta": { "value": -0.05, "real": true },
"platforms": {
"chatgpt": { "tracked": true, "readable": true, "rate": { "value": 0.2, "n": 15 },
"delta": { "value": -0.03, "real": false } },
"perplexity": { "tracked": true, "readable": false, "rate": { "value": null, "n": 0 } }
},
"answers_read": 25,
"failures": 1
}
]
}
```
`total_rows` is how many rows survived the filter and `truncated` whether `limit` cut them, so a short list can be told from a cut one. A tracked platform with no readings in the window appears as `tracked: true, readable: false` with a null rate — present, and not zero.
`area=bot` rows carry `visits`, `pages`, `purpose_mix`, `last_seen` (`null` when it never came) and `agents` instead of the rate fields. A model can only cite a page it has fetched, so check `bot` before concluding a page underperforms on content — but a row here counts only what our middleware recorded, so absence is no proof nothing came.
`competitor` and `source` are workspace-wide aggregates. For the rivals and cited sources of **one** prompt, `GET /v1/prompts/{prompt}` already returns both — no second call needed.
### GET /v1/prompts/
Read-only. No credits. MCP tool: `get_prompt`.
Everything recorded for one prompt: its topic and country, its per-platform rates and delta, the competitors seen in its answers, and the sources those answers cited.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `prompt` | path | string | — | Required. A prompt id, **or** the prompt's exact text, percent-encoded |
| `days` | query | integer | 30 | 1–365 |
```bash Shell theme={null}
curl https://api.attensira.com/v1/prompts/prm_8f21?days=30 \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"id": "prm_8f21",
"text": "best crm for small teams",
"topic": "buying intent",
"country": "US",
"visibility": { "value": 0.12, "n": 25 },
"citation": { "value": 0.04, "n": 25 },
"delta": { "value": -0.05, "real": true },
"position": 2,
"answers_read": 25,
"failures": 1,
"platforms": {
"chatgpt": { "tracked": true, "readable": true, "rate": { "value": 0.2, "n": 15 }, "citation": { "value": 0.07, "n": 15 } }
},
"competitors": [{ "name": "rival.com", "count": 15, "rate": { "value": 0.6, "n": 25 } }],
"sources": [{ "url": "g2.com", "domain": "g2.com", "count": 11, "rate": { "value": 0.44, "n": 25 } }],
"why": ["Named in 3 of 25 answers; rival.com was named in 15."]
}
```
`topic` is `null` when the prompt is not filed under one and `country` is `null` for the workspace default. Competitor and source rates are on this prompt's own `answers_read`.
Passing exact text saves a search round-trip when you already know the wording; inexact text will not match. Percent-encode it — a prompt containing `/` or `?` will otherwise change the route.
### GET /v1/pages
Read-only. No credits. MCP tool: `get_page`.
Everything recorded for one URL on your site: its citation rate overall and per platform, its delta, and the prompts whose answers cited it.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `url` | query | string | — | Required. Absolute URL, exactly as it appears on the site |
| `days` | query | integer | 30 | 1–365 |
```bash Shell theme={null}
curl -G https://api.attensira.com/v1/pages \
--data-urlencode "url=https://acme.com/pricing" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"url": "https://acme.com/pricing",
"title": "Pricing — Acme",
"citations": 5,
"total_answers": 120,
"citation": { "value": 0.04, "n": 120 },
"delta": { "value": null, "real": false },
"platforms": {
"perplexity": { "tracked": true, "readable": true, "rate": { "value": 0.08, "n": 60 }, "citations": 5 },
"chatgpt": { "tracked": true, "readable": true, "rate": { "value": 0, "n": 60 }, "citations": 0 }
},
"prompts": [{ "id": "prm_8f21", "text": "best crm for small teams", "citations": 2, "rate": { "value": 0.08, "n": 25 } }],
"in_sitemap": true
}
```
The `delta` is always `{value: null, real: false}` here: the page fold has no earlier window to compare against, and the contract's word for that is "no basis", not a number.
If this comes back empty, check `GET /v1/analytics/bot` before rewriting the page — a model cannot cite a page it has never fetched.
### POST /v1/evidence/query
Read-only. No credits. MCP tool: `query_evidence`.
Your own data question as a table: one metric, grouped by up to two dimensions, filtered, over a window. It reads the same stored days and folds them the same way as the dashboard boards, so the numbers match what the dashboard shows for the same window.
The body is a JSON object with the same fields, values and limits as the MCP tool: `metric` (required), `group_by`, `days` or `from` and `to`, the filters `topic_ids`, `prompt_ids`, `engines`, `surface`, `country`, `markets`, `property_ids`, `competitor`, `intent`, `stage`, `state`, `domain` and `text_contains`, and `limit`. The [`query_evidence` reference](/mcp/tools#query_evidence) lists every metric with what it can be grouped and filtered by. A field the query does not know is refused rather than ignored, so a misspelt filter cannot come back as unfiltered numbers.
```bash Shell theme={null}
curl https://api.attensira.com/v1/evidence/query \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"metric": "gsc_clicks", "group_by": ["page"], "days": 28, "limit": 2}'
```
```json Response theme={null}
{
"metric": "gsc_clicks",
"group_by": ["page"],
"window": { "from": "2026-09-05", "to": "2026-10-02", "days": 28 },
"day_basis": "search_console",
"unit": "count",
"rows": [
{ "key": { "page": "acme.com/pricing" }, "value": 412, "n": 9310 },
{ "key": { "page": "acme.com/blog/crm-guide" }, "value": 0, "n": 220 }
],
"gaps": [{ "key": "2026-09-30..2026-10-02", "reason": "not_recorded", "detail": "Search Console has not reported these days yet" }],
"truncated": true
}
```
`value` is in `unit`, and `null` means unmeasured, never 0: the row's gap says why. `n` is on every row; for Search Console it is the impressions behind the row. `gaps` names everything the answer could not measure, and an empty list is the claim that nothing is missing. `day_basis` says which calendar the day labels count in (`slot_day`, `search_console` or `utc`; see [Google search](/measure/google-search#day-basis)).
A question with no honest answer — an unknown metric, a metric grouped or filtered in a way it does not support, a `from`–`to` span over 365 days, an id the workspace does not have — is a `400` with `type: "invalid_request"` and a `message` that names the problem and what to ask instead. A question too broad to answer in time is a `422` whose `message` says what to narrow. `days` and `limit` outside their ranges are clamped to the nearest bound rather than refused.
### GET /v1/deciding-sources
Read-only. No credits. MCP tool: `deciding_sources`.
Which pages and domains decided the AI answers, whether you already appear on each, and the next move for each. `GET /v1/analytics/source` ranks by citations; this ranks by influence over a decision. See [Deciding sources](/measure/deciding-sources).
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `prompt` | query | string | — | Optional. One tracked prompt, by id or exact text. Omit for every prompt |
| `days` | query | integer | 30 | 1–365 |
Returns `groups`, most influential first, one per prompt, platform and surface. Each carries a `label`, `n`, `present`, a `recommendation`, and `sources` ranked by weight, each with `domain`, `url`, `citations`, `share`, `present` and `action`. The full row is described under [`deciding_sources`](/mcp/tools#deciding_sources).
### GET /v1/win-plan
Read-only. No credits. MCP tool: `win_plan`.
The ordered plan for winning one tracked prompt. See [Win this answer](/measure/win-this-answer).
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `prompt` | query | string | — | Required. Prompt id or exact text |
| `days` | query | integer | 30 | 1–365 |
Returns `prompt`, `n`, `evidence`, the `steps` in order, `rereads_at` and `credits_estimate`, as described under [`win_plan`](/mcp/tools#win_plan).
***
## The receipts
The numbers above are folds over stored answers. These endpoints hand back the rows themselves, verbatim. See [Deep reads](/agent/deep-read).
### GET /v1/memory/answers/history
Read-only. No credits. MCP tool: `answer_history`.
Every sampled answer to one tracked prompt, verbatim and newest first, with citations and reading ids.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `prompt` | query | string | — | Required. The prompt id |
| `platform` | query | string | — | One platform; omit for all |
| `days` | query | integer | 28 | 1–365 |
| `limit` | query | integer | 10 | Readings per page, 1–50 |
| `cursor` | query | string | — | `next_cursor` from the previous page |
Returns `answers`, `readings`, `has_more` and `next_cursor`. An answer over 8 KB is cut and carries `truncated: true`, with `text_bytes` the full length. A failed sample is listed with `failed: true`, never dropped. The row shape is described under [`answer_history`](/mcp/tools#answer_history).
### GET /v1/memory/answers/slice
Read-only. No credits. MCP tool: `answer_slice`.
Every answer matching a predicate, verbatim. Terms AND together and select the sample; at least one is required.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `platform` | query | string | — | Repeat for several |
| `country` | query | string | — | ISO 3166-1 alpha-2 |
| `topic_id` | query | string | — | The prompts filed under this topic now |
| `prompt_ids` | query | string | — | Repeat, or comma-separate, for several; at most 100 |
| `surface` | query | enum | — | `consumer` or `api` |
| `model` | query | string | — | The start of the model's name, case-insensitive |
| `competitor` | query | string | — | A rival's domain the answer named or linked |
| `cited_domain` | query | string | — | A domain among the citations; subdomains match |
| `brand_named` | query | boolean | — | `true` or `false` |
| `sentiment` | query | enum | — | `positive`, `neutral`, `negative`, `none` (scored, no stance) or `unscored` (never scored) |
| `brand_position_min`, `brand_position_max` | query | integer | — | Your brand's place in the answer's order of brands, 1 for first, inclusive |
| `text_contains` | query | string | — | A phrase in the answer's text, literal and case-insensitive, at most 80 characters |
| `from`, `to` | query | string | — | `YYYY-MM-DD`, inclusive |
| `days` | query | integer | 28 | 1–365, when `from` and `to` are omitted |
| `limit`, `cursor` | query | | | As for answer history |
```bash Shell theme={null}
curl -G https://api.attensira.com/v1/memory/answers/slice \
--data-urlencode "topic_id=top_41a2" --data-urlencode "sentiment=negative" \
--data-urlencode "brand_position_min=3" --data-urlencode "days=60" \
-H "Authorization: Bearer atn_live_"
```
The answer terms (`surface`, `model`, `competitor`, `cited_domain`, `brand_named`, `sentiment`, the brand positions and `text_contains`) must all hold for one sample, and only matching samples come back. Without `topic_id` or `prompt_ids`, `text_contains` covers at most 30 days; a wider window is a `400` saying what to narrow. The full semantics are in the [`answer_slice` reference](/mcp/tools#answer_slice).
### GET /v1/memory/citations/timeline
Read-only. No credits. MCP tool: `citation_timeline`.
One domain's or one page's citations per day, per platform.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `domain` | query | string | — | A site, e.g. `g2.com`. Or pass `url` for one page |
| `url` | query | string | — | One page's URL, instead of `domain` |
| `days` | query | integer | 28 | 1–365 |
Returns `domain`, `url`, `total`, `prompts` and `series`, one entry per platform, each with `points` for every day of the window: `day`, `citations`, `mean_position` (`null` when nothing that day carried a position) and `prompts`. A zero day is a real zero.
### GET /v1/memory/pages/history
Read-only. No credits. MCP tool: `page_history`.
One page's versions: every version captured, when, and what changed between them. It covers your own pages and the cited and competitor pages the workspace watches.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `url` | query | string | — | Required. Absolute: one of your own pages, or a cited or competitor page the workspace watches |
Metadata on a version taken before it was recorded reads `not_captured`, and is never reported as a change. Any other page on another domain is a `404`.
Returns `url`, `known`, and `versions` oldest first, each with what changed since the one before. The row shape is described under [`page_history`](/mcp/tools#page_history).
***
## Track
### POST /v1/prompts
Spends credits. Answers `201`. MCP tool: `add_prompts`.
Starts tracking one or more prompts. From the next daily run, each is read across every platform your plan includes.
| Field | Type | Notes |
| - | - | - |
| `prompts` | string\[] | Required. One or more prompt texts, none blank, at most 50 per call, each at most 500 characters |
| `topic` | string | Optional. Created if it does not exist |
| `country` | string | Optional ISO 3166-1 alpha-2 code, e.g. `US`. Defaults to the workspace's country |
There is no `platform` field. Which surfaces a prompt runs on comes from your plan, not from the request.
```bash Shell theme={null}
curl -X POST https://api.attensira.com/v1/prompts \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"prompts":["best crm for small teams","crm with free tier"],"topic":"buying intent"}'
```
```json 201 Response theme={null}
{
"added": [
{ "id": "prm_8f21", "text": "best crm for small teams" },
{ "id": "prm_2c07", "text": "crm with free tier" }
],
"skipped": [],
"credits_spent": null,
"credits_remaining": null
}
```
A prompt that was already tracked comes back in `added` with its existing id, so a repeated call is idempotent all the way out; `skipped` is empty today. `credits_spent` and `credits_remaining` are `null` because tracking is charged as the prompts are read, not on this call. Your plan caps how many prompts you can hold at once, and a paste that would exceed it is refused with a `409` naming how many slots remain — see [Plans](/account/plans).
### DELETE /v1/prompts/
Destructive. MCP tool: `remove_prompt`.
Permanently stops tracking a prompt and frees its slot. Its historical data goes with it and this cannot be undone — fetch it first if you want it.
| Parameter | In | Type | Notes |
| - | - | - | - |
| `prompt` | path | string | Required. Prompt id |
```json Response theme={null}
{ "id": "prm_8f21", "removed": true }
```
### POST /v1/competitors
Answers `201`. No credits. MCP tool: `add_competitor`.
Registers a brand to detect alongside yours in answers already being collected. It creates no new readings, so it costs nothing.
| Field | Type | Notes |
| - | - | - |
| `domain` | string | Required. Bare hostname, e.g. `rival.com` — no scheme or path |
| `name` | string | Optional display name. Defaults to the domain |
```json 201 Response theme={null}
{ "id": "cmp_10b3", "domain": "rival.com", "name": "Rival" }
```
Competitor rates use the same denominator as your own, so adding one never changes your numbers.
### DELETE /v1/competitors/
Destructive. MCP tool: `remove_competitor`.
Stops scoring a rival. Past readings keep what they already recorded, so history does not move; the rival simply stops being scored from here on.
| Parameter | In | Type | Notes |
| - | - | - | - |
| `id` | path | string | Required. Competitor id, from `GET /v1/analytics/competitor` |
```json Response theme={null}
{ "id": "cmp_10b3", "tracked": false }
```
***
## Automations
### GET /v1/automations
Read-only. No credits. MCP tool: `list_automations`.
Lists every automation with its cadence and last run. No parameters.
```json Response theme={null}
{
"automations": [
{
"id": "aut_71cd",
"name": "Monday visibility report",
"instruction": "summarise last week's visibility and flag anything that moved",
"trigger": "weekly",
"tools": [],
"status": "active",
"timezone": "Europe/London",
"run_at_minute": 540,
"last_run_at": "2026-09-01T08:00:00Z"
}
]
}
```
`status` is `active` (runs on its trigger), `paused` (does not), or `done` (a `once` trigger that already fired). `tools: []` means the default read-only set.
Paused and retired automations are always listed. One the product paused on its own carries `retired_reason` and `retired_at`. `next_run_at` is `null` for an event automation.
### GET /v1/automations/
Read-only. No credits. MCP tool: `get_automation`.
One automation in full, with its most recent run inline — so "did my Monday report run?" costs one call, not two.
```json Response theme={null}
{
"automation": { "id": "aut_71cd", "name": "Monday visibility report", "trigger": "weekly", "status": "active" },
"latest_run": { "id": "ses_9d02", "status": "complete" }
}
```
`latest_run` is `null` when the automation has never run. Pass its `id` to `GET /v1/sessions/{id}` for the full trace.
### POST /v1/automations
Answers `201`. Creating is free; each run it performs spends credits. MCP tool: `create_automation`.
| Field | Type | Default | Notes |
| - | - | - | - |
| `name` | string | — | Required |
| `instruction` | string | — | Required. What it should do each run, in plain language |
| `trigger` | enum | `daily` | `daily`, `weekly`, `monthly`, `quarterly`, `event`, `manual` or `once`. `monthly` and `quarterly` fire on the 1st. `manual` and `once` run only through `POST /v1/automations/{id}/runs`, and `once` then retires |
| `tools` | string\[] | — | Optional allow-list of tool names it may use. Omit to allow everything the plan includes |
| `timezone` | string | The workspace's | Optional IANA zone the schedule is written in, e.g. `Europe/London`. UTC only if the workspace has none |
| `run_at_minute` | integer | 540 | Minutes past local midnight, 0–1439. `540` is 09:00. Only meaningful for the clock triggers: `daily`, `weekly`, `monthly` and `quarterly` |
| `run_on_weekday` | integer | — | For `weekly`: 0 = Sunday to 6 = Saturday |
| `event_types` | string\[] | — | Required and non-empty when `trigger` is `event`; valid only then. Any of `prompt_lost`, `citation_lost`, `new_deciding_source`, `competitor_changed`, `page_changed`. See [Event automations](/agent/autopilot#event-automations) |
```bash Shell theme={null}
curl -X POST https://api.attensira.com/v1/automations \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"name":"Monday visibility report","instruction":"summarise last week","trigger":"weekly","timezone":"Europe/London","run_at_minute":540}'
```
Returns the created automation in the shape shown under `GET /v1/automations`.
```bash Shell — an event automation theme={null}
curl -X POST https://api.attensira.com/v1/automations \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"name":"React to lost citations","instruction":"For each lost citation, propose one fix","trigger":"event","event_types":["citation_lost","prompt_lost"]}'
```
### PATCH /v1/automations/
No credits. MCP tool: `update_automation`.
A partial edit. A field you leave out is left alone, so pausing an automation cannot quietly rewrite what it does. Takes `name`, `instruction`, `trigger`, `event_types`, `tools`, `status` (`active` or `paused`), `timezone`, `run_at_minute` and `run_on_weekday`, with the same rules as `POST /v1/automations`. `event_types` is valid only when the trigger is `event`.
Returns the automation in the shape shown under `GET /v1/automations`.
### POST /v1/automations//runs
Spends credits. Answers `200`. MCP tool: `run_automation`.
Runs an automation immediately, outside its cadence. Returns a session handle, not a finished result.
| Parameter | In | Type | Notes |
| - | - | - | - |
| `id` | path | string | Required |
| `Idempotency-Key` | header | string | Strongly recommended. A repeat call with the same key does not start — or bill — a second run |
```bash Shell theme={null}
curl -X POST https://api.attensira.com/v1/automations/aut_71cd/runs \
-H "Authorization: Bearer atn_live_" \
-H "Idempotency-Key: 2026-09-02-monday-report"
```
```json Response theme={null}
{ "session_id": "ses_9d02", "status": "running" }
```
Poll `GET /v1/sessions/{id}`. Re-sending this `POST` without an `Idempotency-Key` starts a second billed run.
### DELETE /v1/automations/
Destructive. MCP tool: `delete_automation`.
Permanently deletes an automation and its schedule. This cannot be undone — confirm with `GET /v1/automations/{id}` first. Its past runs are kept as sessions, readable through `GET /v1/sessions`.
```json Response theme={null}
{ "id": "aut_71cd", "deleted": true }
```
***
## Inbox
Everything waiting on a person, and every act the dashboard's [Inbox](/agent/inbox) offers on it. Each act goes through the same path as a click in the dashboard, so a yes or an answer means the same thing wherever it was given, and the action log records your key as the one who gave it.
Each row's `type` says which endpoint acts on it, and its `ask_kind` (`approve`, `answer`, `do` or `info`) says what it asks of a person:
| Row | `ask_kind` | Act on it with |
| - | - | - |
| `work_item` | `approve` | `POST /v1/inbox/{id}/approve` (with an edit, for an article, post or rewrite) or `/decline` |
| `work_item` | `do` | Nothing to approve: a copy handoff or an outreach draft the person sends themselves |
| `ask` | `approve` | `POST /v1/inbox/asks/{ask_id}/approve` or `/decline` |
| `ask` | `answer` | Answered in its own session, not here |
| `task` | `answer` | `POST /v1/inbox/tasks/{id}/answer` |
| `task` | `do` | `POST /v1/inbox/tasks/{id}/resolve`, once the person has done it |
Every write needs a read-and-write key; a read key gets `403`. Approve, answer and decline only on the person's instruction. Every act returns `next_item_ref` when something else is waiting: the row the ranking puts first now, readable with `GET /v1/inbox/items/{ref}`.
### GET /v1/inbox
Read-only. No credits. MCP tool: `list_inbox`.
What is waiting on a person: approvals the agent stopped for, questions, work it could not finish alone, failed automations, and approvals that expired unanswered.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `category` | query | string | `needs_you` | `needs_you` (waiting on a person) or `in_flight` (approved and running, and Done rows still being checked) |
| `view` | query | string | `list` | `list` returns every row, newest first, under `items`. `triage` returns the `needs_you` rows as decisions under `triage`, and is `400` with `category=in_flight` |
| `limit` | query | integer | 3 | `view=triage` only: how many decisions are ranked first. Held to 1–20, so a larger number is read as 20; `400` with `view=list` |
Each row in `items` carries `type` (`work_item`, `ask` or `task`), `id`, `ref`, `ask_kind`, `title`, `reason`, `session_id` and `updated_at`, plus `ask_id` on an ask. A task row also carries what it asks: `task.question` (`prompt`, up to six `options`, and `fact_ref` when it fills a profile field), `task.steps`, and `task.verification` while a Done is being checked. `list_approvable` is `true` only on a proposed change that may be approved without opening it: one whose effect can be undone and that is classed safe.
```bash Shell theme={null}
curl "https://api.attensira.com/v1/inbox?view=triage" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"triage": {
"top": [
{
"key": "task:missing_integration:github",
"ask_kind": "do",
"label": "Connect GitHub · 7 waiting",
"count": 7,
"item_refs": ["t_66f1a2", "t_66f1b9", "…"],
"oldest_waiting_since": "2026-09-28T07:12:00Z",
"lead": { "type": "task", "ref": "t_66f1a2", "title": "Connect GitHub", "…": "…" }
}
],
"sections": [
{ "ask_kind": "answer", "label": "2 more questions", "count": 2, "decisions": ["…"] }
],
"total_open": 12,
"decisions": 5,
"merged_count": 9
}
}
```
The triage is the Inbox's own ranking, the same one the dashboard, the morning brief and the daily email read. Rows that wait on the same thing fold into one decision, and `top` holds the first `limit` of them; the rest are in `sections`, grouped by `ask_kind`. `total_open` counts rows, `decisions` counts decisions, and `merged_count` is how many sightings are not shown as rows of their own. See [At most three decisions first](/agent/inbox#at-most-three-decisions-first) for the order.
### GET /v1/inbox/items/
Read-only. No credits. MCP tool: `get_inbox_item`.
One row by its ref, in any state, in full: the whole proposal a work item would make, the question a task asks and its answer once given, a done row as done.
| Parameter | In | Type | Notes |
| - | - | - | - |
| `ref` | path | string | Required. A row's `ref`, or any `next_item_ref`: `t_`, `w_` or `a__` |
To approve a specific version, copy `work_item.change.proposal.captured_at` from here into the approve body. A row the duplicate check folded into another carries `merged_into`, the ref of the row to read instead; one it only suspected carries `possible_duplicate_of` and is not hidden. A malformed ref is `400`; a ref from another workspace is `404`, the same as one that names nothing.
### POST /v1/inbox/tasks//answer
Spends credits when the run that asked had already finished. MCP tool: `answer_inbox_question`.
Answers a task whose `ask_kind` is `answer` with the person's words. The answer is saved to Company facts — the business profile field the question names, or a stated fact every later run reads — the row closes, and the run that asked is put back to work.
| Field | Type | Notes |
| - | - | - |
| `text` | string | Free text, at most 2,000 characters |
| `option` | string | One of the row's `task.question.options`, instead of `text` |
```json Request theme={null}
{ "text": "The launch is on 14 October." }
```
```json Response theme={null}
{
"task": { "id": "66f1c0", "state": "done", "…": "…" },
"fact": { "kind": "stated_fact", "ref": "stated:66f1c3" },
"resume": { "status": "resumed", "session_id": "ses_9d02" },
"next_item_ref": "w_66f0aa"
}
```
`resume.status` is `resumed` (a running run reads the answer from its next step; a finished, failed or suspended one starts a new turn on it, which spends credits), `queued_after_approval` (the run reads it once its pending approval is answered), `not_started` (the run was cancelled, or no run asked) or `failed` (the answer is saved, but the run could not be handed it; retry from the row's page in the dashboard). A second answer to the same row is `409`; a row that is not a question is `400`.
### POST /v1/inbox/tasks//resolve
No credits. MCP tool: `resolve_inbox_task`.
Closes a task the agent raised.
| Field | Type | Notes |
| - | - | - |
| `state` | string | Required. `done` (it has actually been handled), `dismissed` (it does not matter) or `snoozed` |
| `snoozed_until` | string | RFC 3339. Required when `state` is `snoozed`, refused otherwise |
A task that carries a check (a DNS record, a connection, a line on a page) is not closed by `done`: it stays open with `verification.status: "pending"`, moves to `in_flight`, and closes on its own when the check passes. If the check still fails after its retries, the row comes back to `needs_you` with `verification.failure` saying why; sending `done` again checks again. Snoozing a row while its check is pending is `409`. Returns the task and `next_item_ref`.
### POST /v1/inbox//approve
No credits. MCP tool: `approve_work_item`.
Approves a work item and lets the agent carry the change out. The body is optional; no body is a plain yes.
| Field | Type | Notes |
| - | - | - |
| `expected_captured_at` | string | RFC 3339. The `work_item.change.proposal.captured_at` the person read. A proposal revised since is `409`; read the row again with `GET /v1/inbox/items/{ref}` for the current version, and show it to the person before approving that |
| `edited_value` | string | The person's full edit, at most 200 KB. Approves their version instead of the agent's |
Idempotent: approving twice approves once, so a retry after a timeout is safe. Returns the work item and `next_item_ref`.
`edited_value` is accepted only for an article, a LinkedIn post or a rewritten passage that is still waiting, and is `400` for anything else (a fix is edited in its pull request). The edited change keeps the agent's original, and is always a claim: it waits for a person in every approval mode and never goes out on its own. An edit that adds or changes code is `422`, and nothing is approved. An edit is checked against the workspace's rules like any draft; one that breaks a rule is `409`.
### POST /v1/inbox//decline
No credits. MCP tool: `decline_work_item`.
Declines a work item, with the reason why.
| Field | Type | Notes |
| - | - | - |
| `reason` | string | Required. `wrong`, `not_now`, `already_done` or `never_touch` |
| `note` | string | The person's words, at most 1,000 characters. Required with `wrong` |
The reason is what stops the same proposal coming back; the reason and note are kept on the item and in its activity. Returns the work item and `next_item_ref`.
### POST /v1/inbox/asks//approve
No credits for the call; the run it lets go carries on as before. MCP tool: `approve_ask`.
Allows a paused run, once, the exact call it stopped to ask about. `{id}` is the row's `ask_id`.
| Field | Type | Notes |
| - | - | - |
| `session_id` | string | Required. The row's `session_id`: an ask id is unique only within its session |
```json Response theme={null}
{
"ref": "a_66f19d02_ask_66f19d02_3",
"session_id": "66f19d02",
"ask_id": "ask_66f19d02_3",
"outcome": "allowed-once",
"next_item_ref": "t_66f1a2"
}
```
An ask is never edited: the yes is bound to the exact arguments the run asked about, so `edited_value` is `400`. The run is back on the queue and has not made the call yet; poll `GET /v1/sessions/{id}`. A session in another workspace is `404`; an ask already answered, expired, or no longer the one the run is waiting on is `409`.
### POST /v1/inbox/asks//decline
No credits. MCP tool: `decline_ask`.
Refuses the call a paused run asked for. The run reads the reason and note first, then carries on without the call.
| Field | Type | Notes |
| - | - | - |
| `session_id` | string | Required. The row's `session_id` |
| `reason` | string | Required. `wrong`, `not_now`, `already_done` or `never_touch` |
| `note` | string | The person's words, at most 1,000 characters. Required with `wrong` |
Returns `outcome: "rejected"` in the same shape as approve. Same `404` and `409`.
***
## Agent
### POST /v1/ask
Spends credits. Answers `200`. MCP tool: `ask`.
Open-ended investigation the measurement endpoints cannot answer — why a prompt is losing, what to change on a page — run by the Attensira agent, which crawls pages, audits them as a model sees them, and searches the web.
Try a measurement endpoint first: `/v1/analytics/{area}` for any metric or ranked list, `/v1/prompts/{prompt}` for one named prompt, `/v1/pages` for one URL. Those are free and immediate; this costs credits and takes time.
| Field | Type | Notes |
| - | - | - |
| `question` | string | Required. In plain language |
| `session` | string | Optional session id from a previous ask, to continue that thread |
```json Response theme={null}
{
"session_id": "ses_9d02",
"status": "running",
"answer": null,
"poll_after_ms": 4000
}
```
`status` is `complete` (the answer is present), `running` (poll the session after `poll_after_ms`) or `failed` (the run ended without an answer). A `running` response is the success case, not an error.
The agent runs on an **agent model** — the LLM powering the chat. That is a different thing from a tracked **platform**, which is the answer surface Attensira reads on your behalf. Changing your agent model does not change what is measured.
### GET /v1/sessions/
Read-only. No credits. MCP tool: `get_session`.
Fetches a session started by an ask or an automation run: its status and, once finished, its answer. This is how you poll async work.
| Parameter | In | Type | Notes |
| - | - | - | - |
| `id` | path | string | Required |
| `include` | query | string | Optional. `events` returns the step-by-step trace. The only supported value; repeat the parameter or comma-separate |
```bash Shell theme={null}
curl "https://api.attensira.com/v1/sessions/ses_9d02?include=events" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"id": "ses_9d02",
"status": "complete",
"state": "completed",
"answer": "Your pricing page is cited on Perplexity but not on ChatGPT, because…",
"events": [
{ "type": "tool.start", "seq": 4, "at": "2026-09-02T08:00:04Z", "name": "get_page", "callId": "c1" }
]
}
```
`status` is the three-word vocabulary above; `state` is the engine's own finer word, so a run parked on a question for you reads `awaiting_person` under a `running` status. The `events` trace shows which tools the agent called and with what arguments — the place to look when an answer seems wrong and you want to know which slice it read. `answer` is the agent's last message and is filled in for a finished session whether or not the trace was asked for.
### Listing, stopping and feeding sessions
`GET /v1/sessions`, `POST /v1/sessions/{id}/cancel`, `DELETE /v1/sessions/{id}` and `POST /v1/sessions/{id}/context` are on their own page: [Sessions](/api/sessions).
***
## Loop health
### GET /v1/loop-health
Read-only. No credits. Works on a spent balance. MCP tool: `get_loop_health`.
Whether the workspace's autopilot loop is working, idle or blocked, and why, with a day-by-day record. Call it first when nothing seems to be happening. See [Loop health](/agent/loop-health).
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `days` | query | integer | 7 | 1–30 |
```json Response theme={null}
{
"status": "idle",
"reasons": ["2 piece(s) of work are waiting for your approval."],
"days": [
{
"date": "2026-09-22",
"readings": { "chatgpt": { "ok": 40, "failed": 1 } },
"runs": [{ "automation_id": "aut_71cd", "name": "Daily win plan", "status": "fired", "reason": "", "count": 1, "at": "2026-09-22T06:00:00Z" }],
"findings": 1,
"work_produced": 0,
"approvals_pending": 2,
"prs_opened": 0,
"rereads_due": 0
}
]
}
```
`status` is `working`, `idle` or `blocked`. Each `runs` entry has `status` `fired`, `skipped` or `failed`, and a skip carries a `reason`: `no_credits`, `manual_trigger`, `parked_on_ask`, `flag_disabled`, `retired`, `paused`, `cap_reached` or `in_flight`. Every count is read once for its date and is not a trend.
***
## Account
### GET /v1/account
Read-only. No credits. MCP tool: `get_account`.
The org, every workspace the credential can reach, the one this call resolved to, the credit balance, which platforms that workspace tracks, and what this credential may do. Takes an optional `project`.
```json Response theme={null}
{
"org": { "id": "org_3a10" },
"project": { "id": "prj_5b22", "name": "Acme", "domain": "acme.com" },
"workspaces": [
{ "id": "prj_5b22", "name": "Acme", "domain": "acme.com" },
{ "id": "prj_7c41", "name": "Beta", "domain": "beta.example" }
],
"credits": { "available": 4200, "available_usd": 42.0, "held_units": 0 },
"scopes": ["read", "write"],
"platforms": ["chatgpt", "perplexity"]
}
```
Call this before reporting any per-platform number: `platforms` is the authority on which surfaces were queried at all, so a platform missing from it was never measured rather than measured at zero. They belong to the resolved workspace, so the array is empty whenever `project` is `null`.
`project` is `null` when the organisation has several workspaces and the request named none. That is the answer, not an error — `workspaces` carries the ids, and every other endpoint then needs an explicit `?project=`. This is the only endpoint that answers without one, which is why the "several workspaces" error elsewhere points here.
An endpoint that fails with `403` is missing a scope listed in `scopes`.
### GET /v1/costs
Read-only. No credits. Works on a spent balance. MCP tool: `get_costs`.
What a UTC month has cost, in US dollars: the total, a month-end forecast, the spend by project, workspace, kind and deliverable type, and the work the budget committed us to against what was delivered. The same figures as the [Costs](/account/costs) page.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `period` | query | string | this month | `YYYY-MM`, a UTC month that has started. A malformed or future month is a `400` |
| `project` | query | string | — | A workspace id, to narrow the breakdowns to that workspace. **Omit for the whole organisation.** An id outside the organisation is a `404` |
```bash Shell theme={null}
curl "https://api.attensira.com/v1/costs?period=2026-09" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"period": "2026-09",
"currency": "usd",
"as_of": "2026-09-18T09:12:04Z",
"workspace_id": null,
"spent_usd": 642.5,
"forecast_usd": 1108.82,
"by_work_area": [
{ "workspace_id": "prj_5b22", "work_area_id": "wa_19ac", "name": "Pricing pages", "spent_usd": 410.0 },
{ "workspace_id": "prj_5b22", "work_area_id": null, "name": "Unassigned", "spent_usd": 232.5 }
],
"by_workspace": [{ "workspace_id": "prj_5b22", "name": "Acme", "spent_usd": 642.5 }],
"by_kind": [
{ "kind": "agent_work", "spent_usd": 598.1 },
{ "kind": "data", "spent_usd": 44.4 },
{ "kind": "paid_placement", "spent_usd": 0 },
{ "kind": "creators", "spent_usd": 0 }
],
"by_type": [
{ "type": "article", "delivered": 2, "spent_usd": 371.2 },
{ "type": "page_fix", "delivered": 8, "spent_usd": 160.4 },
{ "type": "monitoring_day", "delivered": null, "spent_usd": 44.4 },
{ "type": "other", "delivered": null, "spent_usd": 66.5 }
],
"committed": [
{ "type": "article", "committed": 4, "delivered": 2, "waiting_on_you": 1, "shortfall": 1, "credit_usd": 0 },
{ "type": "page_fix", "committed": 12, "delivered": 8, "waiting_on_you": 0, "shortfall": 4, "credit_usd": 0 },
{ "type": "listing_fix", "committed": null, "delivered": 0, "waiting_on_you": 0, "shortfall": 0, "credit_usd": 0 },
{ "type": "outreach_pitch", "committed": 20, "delivered": 0, "waiting_on_you": 0, "shortfall": 20, "credit_usd": 0 }
],
"credited_back_usd": 0,
"ad_spend": { "usd": null, "source": null, "note": "Not connected. Ad spend is paid to the ad platform directly and is never part of your budget." }
}
```
* `spent_usd` is the month's spend less refunds, and every breakdown adds up to it. Ad spend is never part of it.
* `forecast_usd` is spend divided by the days elapsed, times the days in the month: `null` for the first three days of a month, and what the month cost once it is over.
* A `null` `work_area_id` is a workspace's unassigned spend: chats and the Chief of staff. An empty `workspace_id` in `by_workspace`, named `Across workspaces`, is spend that belongs to the organisation rather than one workspace.
* `by_kind` always lists `agent_work`, `data`, `paid_placement` and `creators`; nothing is charged under the last two today. `unattributed` appears only when a charge could not be placed.
* `delivered` in `by_type`, and `committed` in `committed`, are `null` for a type we do not count. `null` is never `0`.
* `committed` and `credited_back_usd` are the organisation's even when `project` names a workspace. `credit_usd` is the credit for undelivered work: what the close credited, or for an open month what it would credit now. In the example it is `0` because the month's spend has not passed the value of what was delivered. See [Plans](/account/plans#if-we-deliver-less-than-the-budget-committed) for the rule.
The budget, its pace, the caps, the balance and how it is funded are not served here or anywhere a key can reach: a person sets them in **Settings → Billing**. The balance is on [`GET /v1/account`](#get-v1account).
### GET /v1/ai-traffic/install
Read-only. No credits. MCP tool: `get_ai_traffic_install`.
Everything needed to start sending AI-crawler hits: the ingest endpoint, your workspace's HMAC signing secret, the signature header name, the replay window, the daily cap, and a copy-pasteable edge worker. No parameters.
```json Response theme={null}
{
"endpoint": "https://webapp.attensira.com/v1/visits",
"method": "POST",
"signature_header": "X-Attensira-Signature",
"signing_secret": "…",
"project_id": "prj_5b22",
"replay_window_seconds": 300,
"daily_visit_cap": 100000,
"worker": "export default { async fetch(request, env) { … } }",
"notes": ["Deploy in front of the tracked domain."]
}
```
Call it when [AI traffic](/traffic/ai-traffic) is empty and you need to know how to send us any. The signing secret is derived from the workspace id, so it is stable and can be shown again — unlike your API key, which is displayed exactly once.
### GET /v1/wins
Read-only. No credits. Works on a spent balance. MCP tool: `list_wins`.
The workspace's wins, newest first. A win is the first time an engine named your brand, or linked to your site, in an answer to a prompt that does not name your brand itself, after we had read that prompt on that engine on at least three earlier days with a successful answer and none of them did the same. Failed reads count toward neither half. Only the first naming (or link) can be a win: one that came before those three days of reading is not, and no later answer of the same kind on that prompt and engine becomes one.
A win is a first **since we started reading** that prompt, not evidence that any work caused it. When you show one, use its `sentence`, which says exactly that.
No parameters. Returns at most the 50 most recently found wins, and leaves dismissed wins out.
```bash Shell theme={null}
curl https://api.attensira.com/v1/wins \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"wins": [
{
"id": "win_4e19",
"prompt_id": "prm_8f21",
"prompt": "best crm for small teams",
"engine": "ChatGPT",
"platform": "chatgpt",
"kind": "linked",
"sentence": "ChatGPT linked to acme.com for the first time since we started reading this prompt on 2 September 2026, in an answer to 'best crm for small teams' on 28 September 2026.",
"quote": "Acme publishes its pricing openly (https://acme.com/pricing), with a free tier for up to three users.",
"quote_truncated": true,
"cited_url": "https://acme.com/pricing",
"date": "2026-09-28",
"reading_since": "2026-09-02",
"detected_at": "2026-09-28T10:05:00Z",
"seen": true,
"share_url": "https://app.attensira.com/w/"
}
],
"unseen": 0
}
```
`kind` is `named` (the answer named the brand) or `linked` (it cited a page on your domain); one prompt on one engine can give one win of each kind, never two of the same. `quote` is a verbatim excerpt of at most 400 characters, never edited, and `null` when no answer that day gave an excerpt that passed the honesty check; `quote_truncated` says whether it stops short of either end of the answer. `cited_url` is the first page on your domain the answer cited, or `null`. `date` is the day of the answer and `reading_since` the first day we read the prompt on that engine, both `YYYY-MM-DD` in the workspace's time zone. The list is ordered by `detected_at`, when the win was found.
`seen` is whether someone has opened the win in the dashboard, and `unseen` counts every undismissed win nobody has opened, including any beyond the 50 returned. Reading this endpoint marks nothing seen. `share_url` is the public link to the win card while it is shared, and `null` otherwise.
### GET /v1/brief
Read-only. No credits. MCP tool: `get_brief`.
The morning brief, the same body as the email. See [The morning brief](/agent/morning-brief).
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `date` | query | string | — | `YYYY-MM-DD` for one of the workspace's own past days. Omit for today |
Counts, never a day-over-day comparison, until a seven-day window exists. The fields are described under [`get_brief`](/mcp/tools#get_brief).
### PATCH /v1/settings/agents
No credits. MCP tool: `update_settings`.
How far a run may go on its own. A partial edit: a field you leave out is left alone. This is the customer's decision; change it on their instruction. See [Approval](/agent/approval).
| Field | Type | Notes |
| - | - | - |
| `unattended_mode` | enum | `ask`, `draft` or `act` |
| `act_path_allowlist` | string\[] | Path patterns act mode may change. Replaces the list; empty restores the default. Source, CI, lockfiles and dotfiles are refused whatever it says |
| `publish_without_approval` | boolean | Whether a run may put a page on the site without asking |
| `send_without_approval` | boolean | Whether a run may send email without asking |
Returns the settings in force.
### GET /v1/github/install-url
No credits. MCP tool: `connect_github`.
The GitHub App install link for this workspace, and nothing else. The install finishes in a browser, because GitHub hands its callback a code only the person completing it has. Each call mints a single-use link.
Returns `url`.
***
## Memory
Writes into what the workspace knows about itself. See [Knowledge and skills](/agent/knowledge-and-skills).
### POST /v1/observations
Answers `201`. No credits. MCP tool: `record_observation`.
| Field | Type | Notes |
| - | - | - |
| `statement` | string | Required. One sentence carrying its own sample size |
| `evidence` | object\[] | Required, at least one. Each `{kind, id, url?, day?}`, `kind` being `reading`, `citation_day` or `action` |
Every id is resolved against the workspace before anything is stored. One that does not exist there is a `400`, never a stored fact.
### POST /v1/skills
Answers `201`. No credits. MCP tool: `write_skill`.
| Field | Type | Notes |
| - | - | - |
| `title` | string | Required. The rule in a few words |
| `description` | string | Required. When the rule applies |
| `body` | string | Required. The rule itself |
| `kind` | enum | Required. `voice`, `compliance` or `playbook` |
***
## Set up a workspace
A key holder adds a workspace for a domain: the agents draft the business from its site, the person decides what to keep, and the person pays in their own browser. Nothing here can charge. Both endpoints act on the key's own account and take no `?project=`: there is no workspace to select yet, and a `?project=` is refused with that reason. A new person with no account starts from an assistant over MCP instead, where signing in comes before any account exists; see [Set up from your own assistant](/start/quickstart#set-up-from-your-own-assistant).
### POST /v1/onboarding/workspace
Needs a read-and-write key. No charge. MCP tool: `start_workspace`.
Creates the workspace for a domain, or returns the one already started for it. Sending the same domain again never creates a second workspace: it is how you poll.
| Field | Type | Notes |
| - | - | - |
| `domain` | string | Required. The business's domain, e.g. `example.com` |
Returns `workspace_id`, `status`, the drafted profile (each fact with the page it came from, or a `question` to ask when nothing was found), the suggested competitors and platforms, the proposed goals and the plans. `status` is `drafting`, `needs_input`, `ready_to_confirm`, `awaiting_payment`, `active` or `first_readings_ready`; the fields are described under [`start_workspace`](/mcp/tools#start_workspace). An account holds one workspace waiting for payment at a time, so a second unpaid domain is a `409` naming the one to finish.
### POST /v1/onboarding/workspace//confirm
Needs a read-and-write key. No charge. MCP tool: `confirm_setup`.
Saves the person's setup choices and answers their checkout link. Every call mints a new link.
| Field | Type | Notes |
| - | - | - |
| `plan` | string | Required. A plan id from the `plans` the start endpoint returned |
| `monthly_budget_usd` | number | The monthly budget the person chose, in US dollars. Not negative |
| `overage_cap_usd` | number | The most a month may be charged in all, in US dollars: from the budget up to 10% over it, and only sent with `monthly_budget_usd`. A cap above the budget lets new work run up to 10% over the budget, which stays the budget. A cap below the budget or more than 10% over it is a `400`. Omit it and work pauses at the budget, which is then the cap |
| `competitors` | string\[] | The rivals they kept, by domain |
| `platforms` | string\[] | The platforms they kept |
| `goals` | object\[] | The goals they kept, each `{id, target?}` as `proposed_goals` returned them |
| `profile_edits` | object | Corrections to the profile, a fact's field mapped to its new value |
An omitted list keeps what was proposed; an empty list keeps none of it. Returns `checkout_url`, `requires_browser` (always `true`), `expires_at` and `budget_consent`, as described under [`confirm_setup`](/mcp/tools#confirm_setup). An account that already pays for a plan is a `409`. The endpoint never charges and never uses a saved card: the person pays in their browser, and the start endpoint then reads `active`.
***
## Goals
What the agents aim at: a metric, a target and a date, with a pace verdict from the daily readings. See [Goals](/measure/goals).
### GET /v1/goals
Read-only. No credits. MCP tool: `get_goals`.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `status` | query | string | — | `active`, `hit`, `missed`, `paused`, `archived`, or `all`. Repeat it or comma-separate it for several. Omit it for every goal that is not archived |
Returns `goals`, oldest first, each with `baseline` and `current` as `{value, n}` (a `null` value is not measured yet, not zero), `target`, `target_date`, `pace` (`state` is `ahead`, `on_pace`, `behind` or `unmeasured`), the projects it drives as `work_area_ids`, and `status`. The full row is described under [`get_goals`](/mcp/tools#get_goals).
### POST /v1/goals
Needs a read-and-write key. No credits. MCP tool: `set_goal`.
Sets a goal. Set goals on the person's instruction.
| Field | Type | Notes |
| - | - | - |
| `metric` | string | Required. A metric key, such as `ai_share_of_voice` or `search_clicks` |
| `params` | object | What the metric is measured over, where it takes any |
| `target` | number | Required. In the metric's own unit |
| `target_date` | string | Required. `YYYY-MM-DD` |
| `name` | string | Optional |
| `work_area_id` | string | The project it drives. Omit for the whole workspace |
| `Idempotency-Key` | header | Makes a retried create write once |
The baseline is the first reading taken after the goal is set, never a field: a body with `baseline` is a `400`.
### PATCH /v1/goals/
Needs a read-and-write key. No credits. MCP tool: `set_goal`.
Changes a goal. A field you leave out is left alone, and a body with nothing to change is a `400`.
| Field | Type | Notes |
| - | - | - |
| `name` | string | Not empty |
| `target` | number | In the metric's own unit |
| `target_date` | string | `YYYY-MM-DD` |
| `status` | enum | `active`, `paused` or `archived`. `hit` and `missed` are verdicts reached on the target date, never set |
| `work_area_id` | string | A project id, or `null` to make the goal the whole workspace's |
| `version` | integer | The version you read. A goal that changed since is a `409` |
A goal's metric and its params never change: to measure something else, set a new goal and archive this one.
***
## Projects
A project is an area of work with its own crew of agents, and on the wire it is a **work area**. Creating a project and changing its settings are in the dashboard only. See [Projects](/agent/projects).
### GET /v1/work-areas
Read-only. No credits. MCP tool: `list_projects`.
The workspace's projects, each with `id`, `name`, `description`, `team` (each crew member with their status line), `goal_id`, `unread`, `needs_you`, `budget_cap_usd` and `last_activity_at`. Archived projects are left out.
### GET /v1/work-areas/
Read-only. No credits. MCP tool: `list_projects`.
One project: everything the list says, plus its `direction` (what people have told it, word for word), the `triggers` its crew runs on, `automation_ids`, and the `feed`'s newest 50 posts, newest first. A project outside the workspace is a `404`.
### POST /v1/work-areas//steer
Needs a read-and-write key. Posting is free; the crew's reply is an agent run and spends credits. MCP tool: `steer_project`.
Gives a project's crew the person's direction, in their words. It is kept word for word as the project's direction and posted to its feed; the crew answers it with an ordinary run that replans if the steer needs it.
| Field | Type | Notes |
| - | - | - |
| `text` | string | Required. At most 4,000 characters |
| `Idempotency-Key` | header | Makes a retried post post once |
A steer is a post and nothing else: it never approves work, changes a setting or publishes anything, and the run that answers it meets the same approvals as every run. A field other than `text` is refused. The response is described under [`steer_project`](/mcp/tools#steer_project).
***
## Deliverables
### GET /v1/deliverables
Read-only. No credits. MCP tool: `list_deliverables`.
What the crews delivered, newest first: where each piece of work lives and what the re-reads proved about it.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `type` | query | enum | — | `article`, `page`, `fix`, `social_post`, `listing_fix`, `product_copy` or `image` |
| `work_area_id` | query | string | — | One project's deliverables |
| `status` | query | string | — | `waiting_on_you`, `draft`, `open`, `merged`, `synced_draft`, `live`, `shipped` or `closed`. Repeat it or comma-separate it for several |
| `limit` | query | integer | 20 | 1–50 |
| `cursor` | query | string | — | `next_cursor` from the previous page |
Returns `deliverables` and `next_cursor` (`null` at the end). `result` is `null` until there is something to measure. The row is described under [`list_deliverables`](/mcp/tools#list_deliverables).
***
## Reports
### GET /v1/reports/latest
Read-only. No credits. MCP tool: `get_report`.
The latest published client report for a cadence. See [Client reports](/agencies/client-reports).
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `cadence` | query | enum | `week` | `week`, `month` or `quarter` |
Returns the report's period, its facts with their `n`, the share link when it is shared, and the markdown in full: unlike the MCP tool, nothing is cut. A cadence whose first report has not published yet is a `404`.
***
## MCP-only for now
These [MCP tools](/mcp/tools) have no REST endpoint yet. Their payloads are the contract, so when an endpoint lands it will answer the way the tool does today.
| Tool | Does |
| - | - |
| `get_markets`, `preview_markets`, `get_brand_family` | Where the workspace is read, what a markets change would cost, and the brand family. See [Markets](/tracking/markets) and [Brand family](/tracking/brand-family) |
# REST API errors
Source: https://docs.attensira.com/api/errors
Every failure is one envelope with a stable type, so a client needs exactly one branch for things going wrong.
Every non-2xx response from the REST API is the same shape, whether the problem was caught at the door or came back from the backend:
```json Error envelope theme={null}
{
"error": {
"type": "invalid_request",
"message": "unknown area \"vibes\". Valid areas are: visibility, prompt, page, source, competitor, topic, platform, bot. Use \"visibility\" for the overall picture, \"prompt\"/\"page\" for what to fix, \"competitor\"/\"platform\"/\"source\" for where you stand",
"status": 400
}
}
```
There is no second shape and no plain-text response — a `404` for a URL that does not exist at all is still this envelope. One failure branch is enough.
## The fields
| Field | Use it for |
| - | - |
| `type` | Branching. Stable and machine-readable; a value is never renamed. |
| `message` | Showing a human, or handing to an agent to correct itself. Wording changes without notice — never switch on it. |
| `status` | The HTTP status, repeated in the body so a logged payload is self-describing. |
## Types
| `type` | Status | Meaning | Retry? |
| - | - | - | - |
| `invalid_request` | `400`, `413` | The request was wrong: a missing parameter, an unknown enum value, an unknown body field, a body too large. | No — fix the request. |
| `unauthorized` | `401` | Missing, malformed, revoked or expired key. See [Authentication](/api/authentication). | No. |
| `forbidden` | `403` | The key is valid but lacks the scope this endpoint needs. `GET /v1/account` lists the key's scopes. | No. |
| `not_found` | `404` | No such prompt, page, automation, session — or no such endpoint. | No. |
| `conflict` | `409` | The change collides with the current state. | No, not unchanged. |
| `rate_limited` | `429` | Too many requests for this key. | Yes, after a backoff. |
| `server_error` | `5xx` | Something failed on our side. | Yes. |
## Validation errors name the valid values
An error from this API is written to be actionable, not merely correct. Where an enum is wrong, the message lists the accepted values; where a required field is missing, it says what a real value looks like.
```json 400 — bad trigger theme={null}
{
"error": {
"type": "invalid_request",
"message": "trigger is required and must be one of: manual, once, daily, weekly. \"manual\" and \"once\" never fire on a clock; \"daily\" and \"weekly\" fire at run_at_minute",
"status": 400
}
}
```
That is deliberate: the caller is often an agent that can fix its own request in one step if it is told how, and a human reading a `400` in a terminal wants the same sentence.
## What is safe to retry
`429` and `5xx` are the retryable failures. Everything in the `4xx` range below `429` will fail identically until the request changes.
Retry with exponential backoff and jitter. A tight retry loop against a `429` extends the window you are limited for.
These endpoints spend credits: `POST /v1/prompts`, `POST /v1/automations/{id}/runs`, `POST /v1/ask`, `POST /v1/sessions/{id}/context` with `run: true`, `POST /v1/inbox/tasks/{id}/answer` when the run that asked had already finished, and the run that answers `POST /v1/work-areas/{id}/steer`. Send an `Idempotency-Key` header on an automation run so a retry after a timeout cannot start — or bill — a second run.
A `5xx` never echoes an internal message. The body says `temporary server error, retry`, because an internal fault is not something the caller can act on, and the internals of a failure are not theirs to read. The `X-Request-Id` on the response is what identifies it — quote that in a support request.
## Timeouts are not errors
`POST /v1/ask` and `POST /v1/automations/{id}/runs` start work that can outlive the request. They answer `200` with `status: "running"` and a `session_id`; that is the success case, not a failure.
Wait `poll_after_ms`, then poll `GET /v1/sessions/{id}` until `status` is `complete` or `failed`. Re-sending the original `POST` instead starts a second billed run.
Still stuck? [Troubleshooting](/reference/troubleshooting) covers empty results, unexpected zeros and keys that stopped working.
# Attensira REST API
Source: https://docs.attensira.com/api/overview
Read your workspace and change it from your own code, over conventional HTTP, using the same workspace-scoped API key as the MCP server.
The REST API is Attensira for your own code. Same data, same key, and the capabilities the [MCP server](/mcp/overview) exposes, over plain HTTP instead of MCP, so a cron job, a CI step, a dashboard or a data pipeline can reach it without speaking a protocol built for assistants.
## Which door to use
The two surfaces are twins on purpose. Every REST endpoint has an MCP tool with the same payload, so a customer's answer never depends on which door they came through. The few tools without a REST endpoint yet, the markets reads, are listed under [MCP-only for now](/api/endpoints#mcp-only-for-now) and marked MCP-only in the [tool reference](/mcp/tools).
A key holder can add a workspace for a new domain with [`POST /v1/onboarding/workspace`](/api/endpoints#post-v1onboardingworkspace); the person still pays in their own browser. A new person with no account yet starts in the app, or from an assistant over MCP, where signing in comes first.
| Use | When |
| - | - |
| [MCP server](/mcp/overview) | An assistant is doing the work — Claude Code, Cursor, ChatGPT. The assistant discovers the tools and decides which to call. |
| **REST API** | Your code is doing the work — a nightly export, a deploy check, an internal dashboard, a Zapier or n8n HTTP step. |
Both take the same `atn_live_` key, so you do not need a second credential to add the other. The REST API takes a key only; OAuth connections belong to the MCP server.
## Base URL
```text Base URL theme={null}
https://api.attensira.com
```
Every endpoint lives under `/v1`. `https://api.attensira.com/healthz` is unauthenticated and answers without a key, which makes it a useful reachability check when a client is silent about why it failed.
## Your first request
```bash Shell theme={null}
curl https://api.attensira.com/v1/account \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"org": { "id": "org_3a10" },
"project": { "id": "prj_5b22", "name": "Acme", "domain": "acme.com" },
"workspaces": [{ "id": "prj_5b22", "name": "Acme", "domain": "acme.com" }],
"credits": { "available": 4200, "available_usd": 42.0, "held_units": 0 },
"scopes": ["read", "write"],
"platforms": ["chatgpt", "perplexity"]
}
```
Call `/v1/account` first from a fresh integration: it lists every workspace the credential can reach, confirms which one it resolved to, and its `platforms` array is the authority on which AI answer surfaces were queried at all. A platform missing from that list was never measured — which is not the same as measured at zero.
## What the API can do
A key can delete prompts, competitors, automations and sessions, and spend credits. Treat it as a workspace credential, not a read token. The endpoints that spend credits are `POST /v1/prompts`, `POST /v1/automations/{id}/runs` and `POST /v1/ask`, plus `POST /v1/sessions/{id}/context` with `run: true`, `POST /v1/inbox/tasks/{id}/answer` when the run that asked had already finished, and the run that answers a `POST /v1/work-areas/{id}/steer`. The ones that destroy data are `DELETE /v1/prompts/{prompt}`, `DELETE /v1/competitors/{id}`, `DELETE /v1/automations/{id}` and `DELETE /v1/sessions/{id}`.
| Group | Endpoints |
| - | - |
| Find and measure | `GET /v1/search`, `GET /v1/analytics/{area}`, `GET /v1/prompts/{prompt}`, `GET /v1/pages`, `POST /v1/evidence/query`, `GET /v1/deciding-sources`, `GET /v1/win-plan` |
| The receipts | `GET /v1/memory/answers/history`, `GET /v1/memory/answers/slice`, `GET /v1/memory/citations/timeline`, `GET /v1/memory/pages/history` |
| Track | `POST /v1/prompts`, `DELETE /v1/prompts/{prompt}`, `POST /v1/competitors`, `DELETE /v1/competitors/{id}` |
| Automations | `GET`/`POST /v1/automations`, `GET`/`PATCH`/`DELETE /v1/automations/{id}`, `POST /v1/automations/{id}/runs` |
| [Inbox](/api/endpoints#inbox) | `GET /v1/inbox`, `GET /v1/inbox/items/{ref}`, `POST /v1/inbox/tasks/{id}/answer`, `POST /v1/inbox/tasks/{id}/resolve`, `POST /v1/inbox/{id}/approve`, `POST /v1/inbox/{id}/decline`, `POST /v1/inbox/asks/{id}/approve`, `POST /v1/inbox/asks/{id}/decline` |
| Agent | `POST /v1/ask`, `GET /v1/sessions/{id}` |
| [Sessions](/api/sessions) | `GET /v1/sessions`, `POST /v1/sessions/{id}/cancel`, `DELETE /v1/sessions/{id}`, `POST /v1/sessions/{id}/context` |
| Loop health | `GET /v1/loop-health` |
| Account and settings | `GET /v1/account`, `GET /v1/costs`, `GET /v1/ai-traffic/install`, `GET /v1/wins`, `GET /v1/brief`, `PATCH /v1/settings/agents`, `GET /v1/github/install-url` |
| Memory | `POST /v1/observations`, `POST /v1/skills` |
| [Set up a workspace](/api/endpoints#set-up-a-workspace) | `POST /v1/onboarding/workspace`, `POST /v1/onboarding/workspace/{id}/confirm` |
| [Goals](/api/endpoints#goals) | `GET`/`POST /v1/goals`, `PATCH /v1/goals/{id}` |
| [Projects](/api/endpoints#projects) | `GET /v1/work-areas`, `GET /v1/work-areas/{id}`, `POST /v1/work-areas/{id}/steer` |
| Deliverables and reports | `GET /v1/deliverables`, `GET /v1/reports/latest` |
[Webhooks](/api/webhooks) push events to you instead: a session finished, work filed, an article written, a change shipped, a report ready, a scheduled run skipped, a goal updated. They are managed with the same API key at `https://webapp.attensira.com/v1/webhooks`.
The full parameter and response reference is on [Endpoints](/api/endpoints).
## Conventions
**Versioning.** The version is in the path. `/v1` will not change shape under you: fields may be added, never removed or retyped. Write clients that ignore fields they do not recognise.
**Content type.** Requests with a body send `Content-Type: application/json`. Every response is JSON, including every error.
**Unknown fields are rejected.** A `POST` body containing a field the endpoint does not define is a `400`, not a silent drop. A misspelled `instructions` quietly ignored is an automation that does the wrong thing forever; a `400` at the moment of the typo is cheaper.
**Numeric parameters are clamped, not rejected.** `?limit=9999` on an endpoint capped at 50 returns 50 rows, because "as many as you can give me" is what was meant. A value that is not a number at all is a mistake rather than an intent, and returns `400`.
**Paging.** `GET /v1/search` takes `limit` and `offset`, and returns `total` and `has_more`. Page on `has_more` — `total` is a floor, not a grand total, and a full page is not proof there are more. The answer reads under `/v1/memory/answers/` and `GET /v1/deliverables` page with `cursor`: pass the previous page's `next_cursor`, which is `null` at the end. Every other endpoint is bounded by `limit` instead.
**Request ids.** Every response carries `X-Request-Id`. Send your own and it is reused end to end, so one trace stays one trace. Quote it in a support request.
**Naming your client.** Send `X-Attensira-Client: `, for example `n8n`, and the work a call starts is marked with it in the app. It is optional and a label only: it never grants or limits anything. A call without it is shown as API use. See [Marked via your assistant](/mcp/connect#marked-via-your-assistant) for the names shown.
The API is rate limited per key. A `429` comes back in the standard [error envelope](/api/errors) with `type: "rate_limited"` — back off and retry.
## Reading what comes back
Every numeric field can be `null`, and `null` never means zero. A rate is `{value, n}`, a delta is `{value, real}`, and per-platform metrics carry `{tracked, readable}`. `tracked: false` means your plan does not include that platform, so it was never queried — rendering it as 0% invents a failure that did not happen.
This matters more through the API than through the dashboard, because your code is what will coerce a `null` into a `0` on its way into a chart. [Reading the numbers](/measure/reading-the-numbers) covers null against zero, small `n`, and when a delta is worth acting on.
## Next steps
Key format, the header, what a 401 means, and how scoping works.
Every endpoint, with request and response examples.
One envelope, seven types, and what to retry.
# Sessions
Source: https://docs.attensira.com/api/sessions
List the agent's sessions, stop one, delete one, or hand it more context: the REST endpoints behind list_sessions, cancel_session, delete_session and add_context.
A **session** is one piece of agent work: an `ask`, an automation run, a chat, or the [set-up run](/start/set-up-run). `GET /v1/sessions/{id}` reads one (see [Endpoints](/api/endpoints#get-v1sessionsid)). The endpoints on this page list them and act on them.
Each has an MCP twin with the same payload: `list_sessions`, `cancel_session`, `delete_session` and `add_context`. See the [tool reference](/mcp/tools#sessions).
## GET /v1/sessions
Read-only. No credits. MCP tool: `list_sessions`.
Sessions, newest first.
| Parameter | In | Type | Default | Notes |
| - | - | - | - | - |
| `status` | query | string | — | `complete`, `running` or `failed`, or a stored state: `queued`, `running`, `awaiting_person`, `suspended`, `completed`, `failed`, `cancelled`. Comma-separate or repeat for several |
| `origin` | query | string | — | `user`, `automation`, `mcp` or `setup`. Comma-separate or repeat |
| `since` | query | string | — | RFC 3339 timestamp or `YYYY-MM-DD` |
| `limit` | query | integer | 50 | 1–200 |
```bash Shell theme={null}
curl -G https://api.attensira.com/v1/sessions \
--data-urlencode "status=running" --data-urlencode "origin=automation" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"sessions": [
{
"id": "ses_9d02",
"title": "Daily win plan",
"origin": "automation",
"status": "running",
"state": "running",
"automation_id": "aut_71cd",
"created_at": "2026-09-23T06:00:02Z",
"ended_at": null
}
]
}
```
`status` is the three-word vocabulary (`complete`, `running`, `failed`); `state` is the finer stored word. A cancelled session reads `failed` under `status` and `cancelled` under `state`.
## POST /v1/sessions/\{id}/cancel
No credits. Works on a spent balance. MCP tool: `cancel_session`.
Stops a running session before its next step. Credits already spent on it stay spent.
Idempotent: cancelling twice is the same as cancelling once. If the run already finished, the call returns that real ending unchanged; a cancel never rewrites a finished run.
```json Response theme={null}
{ "id": "ses_9d02", "status": "failed", "state": "cancelled" }
```
## DELETE /v1/sessions/\{id}
Destructive. MCP tool: `delete_session`.
Permanently deletes a finished session and its trace. A running session answers `409`: cancel it first.
```json Response theme={null}
{ "id": "ses_9d02", "deleted": true }
```
## POST /v1/sessions/\{id}/context
Spends credits only with `run: true`. MCP tool: `add_context`.
Gives a session text or links it cannot find itself. The agent reads it as a message in the thread.
| Field | Type | Notes |
| - | - | - |
| `text` | string | At most 20,000 characters |
| `urls` | string\[] | `http` or `https`, at most 10 |
| `run` | boolean | Default `false` |
Send `text`, `urls`, or both.
* **`run: false`** (default): nothing starts. The session's next turn reads the context, and a run already in flight picks it up when its current turn ends.
* **`run: true`**: the session is queued again to act on it now. That is a new turn, and it costs credits.
A cancelled session refuses new context.
```bash Shell theme={null}
curl -X POST https://api.attensira.com/v1/sessions/ses_9d02/context \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"text":"Our pricing page moved to /plans last week.","urls":["https://acme.com/plans"],"run":false}'
```
```json Response theme={null}
{ "session_id": "ses_9d02", "status": "running" }
```
# Webhooks
Source: https://docs.attensira.com/api/webhooks
Get a signed HTTPS POST when a session finishes, work is filed, an article is written, a change ships, a report is ready, a scheduled run is skipped or a goal changes: endpoints, events, signatures, retries and the delivery log.
Webhooks push events to your own endpoint instead of making you poll. Each delivery is a signed `POST` with a JSON body.
## Managing endpoints
Manage endpoints in the dashboard, or from your own code with an **API key**:
| Door | Routes | Credential |
| - | - | - |
| Dashboard | `/v2/org/webhooks` | Your signed-in session |
| Your code | `https://webapp.attensira.com/v1/webhooks` | An `atn_live_` [API key](/account/api-keys). OAuth tokens are refused: creating an endpoint mints a signing secret, and an OAuth consent must not mint a credential |
Both doors take the same calls:
| Call | Does |
| - | - |
| `GET /v1/webhooks` | Lists the org's endpoints, secrets masked, each with its last delivery status |
| `POST /v1/webhooks` | Creates an endpoint. Answers `201` |
| `PATCH /v1/webhooks/{id}` | Edits `url`, `events`, `description`, `project_id` or `enabled` |
| `DELETE /v1/webhooks/{id}` | Deletes an endpoint. Deliveries already queued for it are dropped |
| `POST /v1/webhooks/{id}/test` | Sends a `webhook.test` delivery. Answers `202` with `{delivery_id, queued}` |
| `GET /v1/webhooks/events` | Lists the event types you can subscribe to, as `{events: [{type, description}]}` |
| `GET /v1/webhooks/{id}/deliveries` | The endpoint's [delivery log](#the-delivery-log), newest first |
| `POST /v1/webhooks/{id}/deliveries/{deliveryId}/resend` | [Sends a logged delivery again](#resending-a-delivery). Answers `202` with `{delivery_id, resend, queued}` |
An org can have at most **20** endpoints.
### Endpoint fields
| Field | Notes |
| - | - |
| `url` | Required. `https` only, on a public host |
| `events` | Which events to send. Empty or `*` means all |
| `project_id` | Optional. Only send events for this workspace |
| `description` | Optional. For your own reference |
| `enabled` | Whether deliveries are sent |
```bash Shell theme={null}
curl -X POST https://webapp.attensira.com/v1/webhooks \
-H "Authorization: Bearer atn_live_" \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.example.com/attensira","events":["session.finished","work_item.created"],"description":"Ops channel"}'
```
The response to the create call is the **only time the signing secret is shown** in full. Store it then; every later read shows it masked.
An endpoint also reports how deliveries are going: `last_delivery_at`, `last_status`, `last_error` and `failing_since`. If we turned it off, `disabled_reason` and `disabled_at` say why and when (see [When an endpoint is turned off](#when-an-endpoint-is-turned-off)).
## Events
| Event | Sent when | `data` |
| - | - | - |
| `session.finished` | A session ends | `session_id`, `status` (`complete` or `failed`), `state` (`completed`, `failed` or `cancelled`), `origin`, `title`, `automation_id`, `created_at`, `ended_at` |
| `work_item.created` | The agent files a piece of work that waits on a person | `work_item_id`, `title`, `kind`, `target` |
| `document.created` | The agent writes a document for publishing, such as an article, and files it for approval | `document_id`, `version`, `session_id`, `project_id`, `work_item_id`, `title`, `kind`, `target_plugin`, `body_markdown`, `body_truncated`, `dashboard_url` |
| `change.shipped` | A change goes live | `work_item_id`, `project_id`, `kind`, `plugin`, `url`, `shipped_at`, `delivery_label` |
| `report.ready` | A [client report](/agencies/client-reports) is published | `report_id`, `kind`, `title`, `url` |
| `loop.skipped` | A scheduled automation run is skipped (see [loop health](/agent/loop-health#why-a-scheduled-run-was-skipped)) | `automation_id`, `name`, `reason`, `due_at` |
| `goal.updated` | A [goal](/measure/goals) is set or edited, paused, resumed or archived, or its pace or status changes | `goal_id`, `name`, `change`, `status`, `pace_state`, `previous_pace_state`, `current`, `target`, `target_date`, `work_area_ids` |
| `webhook.test` | You asked for a test delivery | `message` |
`GET /v1/webhooks/events` returns the same list, each with a one-line description, so your code can read what it may subscribe to. New event types are added at the end; a receiver should ignore a type it does not know.
**`work_item.created`.** `kind` is the shape of the work, such as `fix`, `rewrite`, `document`, `social_post`, `answer` or `outreach`. `target` is where approving it delivers, such as `github`, `notion`, `webflow`, `copy` (you publish it yourself) or `email`, and is empty when approving it delivers nowhere on its own.
**`document.created`.** Sent once per version of a document that became work: the agent revising an article into a new version sends it again, and a document kept beside a chat is not sent at all. `target_plugin` is where approving it delivers: `github`, `notion`, a CMS such as `webflow`, or `copy`. `body_markdown` is the whole document, up to **256 KB**. Past that it is `null` and `body_truncated` is `true`, rather than a document cut off mid-sentence; `dashboard_url` opens the chat that wrote it, where the whole document is. `session_id` and `work_item_id` may be `null`.
**`change.shipped`.** Sent once per change, however it went live: its pull request merged, an integration published it, or someone [marked it shipped](/agent/inbox#mark-shipped). `url` is where it can be opened and `delivery_label` says where it lives in words, such as "Pull request #17 on GitHub" or "Draft in Webflow"; either may be `null`. A change that landed as a draft in your own tool is not shipped until it is marked shipped.
**`goal.updated`.** `change` says what happened: `created`, `updated`, `pace_changed`, `hit`, `missed`, `paused`, `resumed` or `archived`. `pace_state` is `ahead`, `on_pace`, `behind` or `unmeasured`, and `previous_pace_state` is present only when the pace moved. `current` is `{value, n}`, with `value` `null` until the first reading. A daily reading that changes neither pace nor status sends nothing.
Payloads link to the dashboard and to where the work lives. They never carry a link that approves or merges anything: that happens where a person reads it first.
## A delivery
Each delivery is a `POST` to your URL with a JSON body:
```json Body theme={null}
{
"id": "evt_01J8Z3",
"type": "session.finished",
"created_at": "2026-09-23T06:04:11Z",
"project_id": "prj_5b22",
"data": {
"session_id": "ses_9d02",
"status": "complete",
"state": "completed",
"origin": "automation",
"title": "Daily win plan",
"automation_id": "aut_71cd",
"created_at": "2026-09-23T06:00:02Z",
"ended_at": "2026-09-23T06:04:10Z"
}
}
```
With these headers:
| Header | Value |
| - | - |
| `Attensira-Signature` | `t=,v1=` |
| `Attensira-Event` | The event type |
| `Attensira-Delivery` | The delivery id, the same as the body's `id`. Stable across retries and resends, so dedupe on it |
## Verifying the signature
`v1` is the hex HMAC-SHA256, keyed with the endpoint's signing secret, of the string `.`: the `t` value, a full stop, and the raw request body exactly as received.
1. Split the header on `,` and read `t` and `v1`.
2. Compute the HMAC over `t + "." + rawBody`.
3. Compare it with `v1` in constant time.
4. Reject the delivery if `t` is more than **5 minutes** old.
```js Node theme={null}
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
```
## Retries
Answer with any `2xx` to accept a delivery. Any other status, or no answer within **10 seconds**, is retried up to **14 times** with exponential backoff, spread over about a day. Redirects are not followed: a `3xx` counts as a failure. A `410 Gone` is not retried: it [turns the endpoint off](#when-an-endpoint-is-turned-off).
Because a retry carries the same `Attensira-Delivery`, a receiver that stores the ids it has handled can treat a repeat as already done.
**One occurrence, one id.** Events that name a single occurrence (a work item filed, a document version, a change shipped, a report, a skipped run, a goal transition) get an id derived from it. If the same occurrence is reported to us twice, for example GitHub redelivering a merge, it renders the same id, and is not queued again while the first delivery is still held (up to a day after it finishes). Past that it may be delivered again under the same id, which is why deduping on `Attensira-Delivery` is the reliable rule.
## The delivery log
Every delivery to an endpoint is logged for **30 days**: what was sent and each attempt to send it. Read it in **Settings → Webhooks** under the endpoint's **Delivery log**, or with:
```bash Shell theme={null}
curl "https://webapp.attensira.com/v1/webhooks//deliveries?limit=20" \
-H "Authorization: Bearer atn_live_"
```
```json Response theme={null}
{
"deliveries": [
{
"id": "evt_5f0c2a9e41b7d3c8a1e6f204",
"type": "change.shipped",
"project_id": "prj_5b22",
"created_at": "2026-09-30T09:12:04Z",
"state": "failed",
"last_status": 503,
"resends": 0,
"attempts": [
{
"at": "2026-09-30T09:12:05Z",
"status": 503,
"duration_ms": 412,
"response_snippet": "Service Unavailable",
"error": "webhook receiver did not accept the delivery: HTTP 503",
"resend": 0
}
]
}
],
"next_cursor": null
}
```
* `id` is the delivery id your server saw in `Attensira-Delivery`.
* `state` is `queued` until the first attempt, then `delivered` or `failed` according to the latest attempt.
* Each attempt records when it was made, the status your server answered (`0` when it did not answer), how long it took, and the first **2 KB** of your server's answer. Anything in that answer that looks like a secret, a token, a password or an email address is masked before it is stored. `resend` is `0` for the original send and `n` for the nth resend. A row keeps its latest 20 attempts.
* Pages are newest first. `limit` defaults to 20, up to 100; pass `next_cursor` back as `cursor` for the next, older page. It is `null` on the last page.
## Resending a delivery
`POST /v1/webhooks/{id}/deliveries/{deliveryId}/resend`, or **Resend** in the delivery log, queues a logged delivery again. It sends the same body with the same `Attensira-Delivery` id, signed afresh with a new `t`, so a receiver that already handled it treats it as done. A resend is never refused for being too soon after the original. Its attempts are added to the same log row.
A resend is refused with `409` while the endpoint is turned off, and with `404` once the delivery is older than the 30 days the log keeps.
## When an endpoint is turned off
We stop sending to an endpoint, and turn it off, when:
* your server answers `410 Gone`, which says the endpoint no longer exists; or
* a delivery fails and every delivery has failed for more than **72 hours**.
Turning it off sets `enabled` to `false`, records `disabled_reason` and `disabled_at`, and raises one [Inbox](/agent/inbox) row, "Your webhook to `` was turned off", in the workspace the event was about (or the endpoint's workspace, or your oldest workspace for an org-wide endpoint). Deliveries already queued for it are logged as not sent.
Nothing more is sent until someone turns it back on, in **Settings → Webhooks** or with `PATCH /v1/webhooks/{id}` and `{"enabled": true}`. That clears `disabled_reason`, `disabled_at` and `failing_since`, so the 72-hour clock starts again. Then resend what it missed from its delivery log. A test delivery is sent even while the endpoint is off, so you can check your server before turning it back on.
# Attensira documentation
Source: https://docs.attensira.com/index
The AI-native SEO agency: agents do the work of getting you found in Google, AI assistants and the sites they read, and you steer and approve.
Attensira is the AI-native SEO agency. Search everywhere: Google, the AI assistants (ChatGPT, Claude, Perplexity, Gemini and Google's AI), and the sites and communities those assistants read.
It is run by AI agents. You talk to one of them, the **Chief of staff**, and set the direction. Crews of agents do the work in [projects](/agent/projects): research, articles, page fixes, outreach drafts and follow-ups. They stop only when they need you, and anything that cannot be undone waits for your yes. You pay a plan fee for the platform and a monthly budget for the work, and work we don't deliver isn't charged.
Underneath the work sits measurement. Every day we put the questions your buyers ask to real assistants and record whether you were named and whether you were linked, so every change can be read before and after it ships. We promise the work, never the outcome, and we say "nothing moved that we can prove" when that is the honest answer.
Who does what, what reaches you, what needs your yes, and what we promise.
From your domain to a team at work: the set-up steps, the plan and budget, and the first readings.
Chief of staff, project, deliverable, goal, Inbox, prompt, answer: every word, and its name on the wire.
The plan fee, the monthly work budget and what it buys, and what happens when we deliver less.
Connect your own assistant. It can set up a workspace, read the numbers and act, with the same approvals.
Workspace data and actions as plain HTTP endpoints, for your own code rather than an assistant.
The cadence, the sampling depth, and what counts as a successful run.
What Attensira does not measure or promise, and where the numbers stop being trustworthy.
# Connect a client
Source: https://docs.attensira.com/mcp/connect
Complete, copy-pasteable Attensira MCP setup for Claude Code, Claude Desktop, Claude.ai, Cursor, VS Code, ChatGPT, Codex CLI, Windsurf, Zed, Cline, n8n, and any other MCP client.
Every client below connects to the same endpoint. What changes is where you put the URL, and which of the two credentials you use.
The quickest path is in the app: **Settings → Your assistant** at [app.attensira.com/settings/access](https://app.attensira.com/settings/access). Pick the assistant you use (Claude, ChatGPT, Cursor, Claude Code, Codex or VS Code) and you get that one's steps, ready to copy, with nothing else to read through. Claude and ChatGPT connect only by signing in, so they are listed only while sign-in is available. This page has the same steps plus every other client.
| Setting | Value |
| - | - |
| Server URL | `https://mcp.attensira.com/mcp` |
| Transport | Streamable HTTP |
| Authentication | OAuth (sign in through the browser) **or** `Authorization: Bearer atn_live_` |
## Which credential to use
Both work, on the same endpoint, and neither is going away.
| | OAuth | Static API key |
| - | - | - |
| Use it for | Interactive clients with a human and a browser — Claude, Claude Code, Cursor, VS Code, ChatGPT | Headless callers — n8n, CI jobs, scripts, servers |
| Setup | Paste the URL and sign in | Mint a key, paste it into a header |
| Secret on disk | None. The client stores a token it can refresh | Yes. A long-lived key sits in a config file or a secret store |
| Expiry | Access tokens are short-lived and refresh silently; you sign in again occasionally | Never expires until you revoke it |
| Taking it away | Remove the server in the client | Revoke it in Settings → Your assistant, effective on the next request |
If a human is going to be sitting there when the client first starts, use OAuth: there is no key to paste, nothing to leak into a committed config file, and revoking one client does not break the others. If nothing can open a browser, use a key.
You can do both at once. Claude Code on your laptop over OAuth and an n8n workflow on a static key are two independent credentials against the same server, and revoking either leaves the other alone.
## Which clients need a bridge
The Attensira server speaks **Streamable HTTP only**. Clients that speak remote HTTP natively connect directly. Clients that only launch local stdio processes cannot, and need the `mcp-remote` bridge, which runs locally over stdio and forwards to the HTTPS endpoint.
| Client | Connects directly | OAuth | Notes |
| - | - | - | - |
| Claude Code, Claude Desktop, Claude.ai | Yes | Yes | Native remote HTTP and OAuth |
| Cursor, VS Code + Copilot | Yes | Yes | Browser prompt on first connect |
| ChatGPT | Yes | Yes | Connector configured in the UI |
| Windsurf, Cline | Yes | Varies by build | Config file takes a `url` and `headers`; use a key if the build has no OAuth |
| Codex CLI | Yes | No | `codex mcp add --bearer-token-env-var` keeps the key out of config |
| n8n | Yes | No | Headless — use a static key |
| Zed | Yes | Yes | Remote server with a `url`; Zed runs OAuth when there is no `Authorization` header |
| Older client builds that launch local processes only | Via `mcp-remote` | Yes, through the bridge | stdio-only; see the bridge section |
***
# Connecting with OAuth
Nothing to mint and nothing to paste. You add the URL, a browser opens, you sign in and choose a workspace, and the client stores a token it refreshes on its own.
## Claude.ai and Claude Desktop
Go to **Settings → Connectors → Add custom connector**, name it `Attensira`, and enter:
```text Server URL theme={null}
https://mcp.attensira.com/mcp
```
Leave every authentication field alone — do not add a header, and do not paste a client ID or secret. Choose **Connect**, and Claude sends you to Attensira to sign in. Approve the access it asks for, and you land back in Claude with the connector enabled. The connection reaches every workspace your organisation owns; you name one in the conversation when there are several.
You do not need a workspace first. Once you are signed in, ask Claude to set one up for your domain, and it walks you through it — see [Set up a business from your assistant](/mcp/recipes#set-up-a-business-from-your-assistant).
Open a new chat and enable Attensira from the tools menu. Existing conversations do not pick up new connectors. The connector works in claude.ai, Claude Desktop and the Claude mobile apps.
Availability of custom connectors depends on your Claude plan. On Team and Enterprise, an owner adds the connector first, under Organization settings → Connectors.
## Claude Code
Add the server with no credential at all, then authenticate from inside Claude Code:
```bash Shell theme={null}
claude mcp add --transport http attensira https://mcp.attensira.com/mcp
```
Then run `/mcp`, select **attensira**, and choose to authenticate. A browser opens, you sign in and approve, and the callback returns to a local port Claude Code is listening on.
Add `--scope user` to make the server available in every project rather than only the current one. Verify with `claude mcp list`, then ask Claude to call `get_account` — a successful reply lists the workspaces the credential can reach.
Two Claude Code instances can authenticate at the same time. Each listens on its own ephemeral loopback port, and both are accepted.
## Cursor
**Add to Cursor** — one-click install link. Cursor must already be installed; clicking it opens Cursor and prompts you to add the server. It writes the URL and nothing else — no key, and nothing is read or transmitted.
You can also add it by hand. Create `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` for all projects. With OAuth there is no secret in the file, so the in-repo path is safe:
```json .cursor/mcp.json theme={null}
{
"mcpServers": {
"attensira": {
"url": "https://mcp.attensira.com/mcp"
}
}
}
```
Reload Cursor and open Settings → MCP. The entry shows as needing authentication; click through it, sign in and approve, and the tools fill in.
## VS Code + Copilot
VS Code reads `.mcp.json` at the workspace root or `.vscode/mcp.json`. With OAuth the whole file is three lines and carries nothing secret:
```json .vscode/mcp.json theme={null}
{
"servers": {
"attensira": {
"type": "http",
"url": "https://mcp.attensira.com/mcp"
}
}
}
```
Start the server from the Start link above the entry. VS Code opens a browser for sign-in and stores the resulting token in its own account storage, not in the file. Then open Copilot Chat in Agent mode and check the tools picker.
## ChatGPT
In ChatGPT on the web, open **Settings** and turn on **Developer mode**.
Create an app, name it `Attensira`, enter `https://mcp.attensira.com/mcp` and choose **OAuth**. Attensira publishes standard OAuth discovery, so there is no client ID or secret to fill in.
Sign in to Attensira and allow. The connection reaches every workspace your organisation owns.
In a chat, open the **+** menu, choose Developer mode and turn Attensira on.
Developer mode depends on your ChatGPT plan and, on Business and Enterprise, on what an admin allows. Custom MCP connectors in ChatGPT depend on your plan and workspace settings, and some surfaces restrict which tools a connector may call. If write tools such as `add_prompts` are unavailable, that is a ChatGPT-side restriction, not a scope problem — check [Connections](/account/connections) to see what you actually granted before assuming otherwise.
## Zed
Current Zed builds connect to remote MCP servers directly. In **Settings → AI → MCP Servers**, choose **Add Server → Add Remote Server**, or add the entry to your settings file yourself:
```json Zed settings.json theme={null}
{
"context_servers": {
"attensira": {
"url": "https://mcp.attensira.com/mcp"
}
}
}
```
With no `Authorization` header, Zed runs the standard MCP OAuth flow itself: it opens a browser, you sign in and approve, and the tools appear in the Agent Panel. To use a key instead, add `"headers": { "Authorization": "Bearer atn_live_..." }` to the same entry. The indicator next to the server in Settings → AI → MCP Servers turns green when it is active.
## Older builds and stdio-only clients
A client, or an older Zed build, that can only launch local processes needs the `mcp-remote` bridge. The bridge runs locally over stdio, forwards to the HTTPS endpoint, runs the OAuth flow itself and caches the token under your home directory:
```json Zed settings.json (older builds) theme={null}
{
"context_servers": {
"attensira": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.attensira.com/mcp"]
}
}
}
```
The bridge needs Node.js. Its first run downloads the package and opens a browser, so allow a few seconds and expect a sign-in prompt before the tools appear.
## What you are approving
The consent screen names the application and the access it wants. Read both.
An OAuth connection always carries **read and write**: there is no read-only consent. Write covers everything that changes the workspace or spends credits — adding prompts, creating and running automations, deleting things, asking the agents, approving or declining what they propose. A client that can only read cannot do the half of the job you connected it for. Where you need a credential that cannot write, mint a read-only [API key](/account/api-keys) instead.
Anyone can register an MCP client and pick its display name. Read the name before approving, and be sure about anything unfamiliar. If you did not just start a connection yourself, do not approve one — close the tab.
What an approved client can reach, and how to disconnect one, is in [Connected applications](/account/connections).
***
# Connecting with a static key
This is the path for anything without a human and a browser: n8n, CI, a scheduled script, a server-side agent. It is fully supported and is not going anywhere.
## Get a key first
Mint a key under **Settings → Your assistant** at [app.attensira.com/settings/access](https://app.attensira.com/settings/access). It is shown exactly once: only a hash is stored, so Attensira cannot re-display it. Copy it somewhere safe — a password manager or your shell environment — before you close the dialog.
The key names your workspace, so no tool asks for a workspace id. If you lose it, revoke and mint a new one; there is no recovery path.
Export it once and reference it by name wherever a client supports environment variables:
```bash Shell theme={null}
export ATTENSIRA_API_KEY="atn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```
Put that line in your shell profile so GUI clients launched from a terminal inherit it.
`.cursor/mcp.json`, `.vscode/mcp.json`, and any config file inside a repository are shared files. A key pasted into one and committed is a leaked key — assume it is public the moment it is pushed, revoke it, and mint a new one. Use the environment-variable form in repo-visible files, add local overrides to `.gitignore`, or use OAuth, which puts no secret in the file at all.
## n8n
Use the **MCP Client Tool** node inside an AI Agent workflow. Set the endpoint to `https://mcp.attensira.com/mcp` and the transport to HTTP Streamable — not SSE, which the server does not offer.
For credentials, choose the Bearer or header-auth credential type and store the key there. n8n credentials are encrypted at rest and are not written into the workflow JSON, so an exported or version-controlled workflow does not carry the key. Once connected, the node exposes the Attensira tools to the agent; restrict the tool list on the node if you do not want the agent reaching credit-spending tools such as `run_automation`.
This is exactly the case OAuth cannot serve: a workflow fires at 3am with nobody to approve a browser prompt. Use a key, give it the narrowest scope the workflow needs, and name it after the workflow so revoking it later is unambiguous.
## Codex CLI
Codex reads the token from a named environment variable at runtime and sends it as the `Authorization` header itself, so the key never enters `~/.codex/config.toml`:
```bash Shell theme={null}
codex mcp add attensira --url https://mcp.attensira.com/mcp \
--bearer-token-env-var ATTENSIRA_API_KEY
```
That writes an `mcp_servers.attensira` table to `~/.codex/config.toml` without the key. Make sure `ATTENSIRA_API_KEY` is set in the shell profile Codex launches from.
Only if your Codex build does not support `--bearer-token-env-var`, edit `~/.codex/config.toml` directly with the key written literally:
```toml ~/.codex/config.toml theme={null}
[mcp_servers.attensira]
url = "https://mcp.attensira.com/mcp"
[mcp_servers.attensira.http_headers]
Authorization = "Bearer atn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```
Restart Codex after editing the file.
## Windsurf
Windsurf reads `~/.codeium/windsurf/mcp_config.json`:
```json ~/.codeium/windsurf/mcp_config.json theme={null}
{
"mcpServers": {
"attensira": {
"serverUrl": "https://mcp.attensira.com/mcp",
"headers": {
"Authorization": "Bearer atn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
```
If your Windsurf version rejects `serverUrl`, use `"url"` instead — the two spellings have both shipped. Press the refresh control in the Cascade MCP panel after saving. Since this file lives in your home directory, it is not part of any repository, but it is still plaintext on disk.
## Cline
In VS Code, open Cline → MCP Servers → Configure, and add a remote entry:
```json cline_mcp_settings.json theme={null}
{
"mcpServers": {
"attensira": {
"type": "streamableHttp",
"url": "https://mcp.attensira.com/mcp",
"headers": {
"Authorization": "Bearer atn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
```
Cline also has a "Remote Servers" tab where you can paste the URL and header without editing JSON. If your Cline version offers only stdio entries, use the `mcp-remote` command shown below.
## Claude Desktop and Claude Code with a key
Both support OAuth, and OAuth is the better path on a machine with a browser. Use a key only where you deliberately want a fixed credential — a shared build machine, or a container without an interactive session.
```bash Claude Code theme={null}
claude mcp add --transport http attensira https://mcp.attensira.com/mcp \
--header "Authorization: Bearer $ATTENSIRA_API_KEY"
```
Claude Desktop does not expand shell variables in `claude_desktop_config.json`, so the key is written literally — protect the file with filesystem permissions and never copy it into a repository:
```json claude_desktop_config.json theme={null}
{
"mcpServers": {
"attensira": {
"url": "https://mcp.attensira.com/mcp",
"headers": {
"Authorization": "Bearer atn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
```
Restart the app afterwards. The Attensira tools appear in the tool menu of a new conversation; existing conversations do not pick up new servers.
## Marked via your assistant
Anything your assistant files in your workspace (an Inbox row, a change to approve, a question) is marked with the client it came through, such as **via Claude** or **via Cursor**. The Inbox gathers those rows under **From your assistant**, and an approval or decline your assistant makes is recorded in the item's history the same way, for example "OAuth connection via Claude". You can tell at a glance which items you asked your assistant for and which the agents raised.
The name comes from the client itself. When a client connects to the MCP server it says what it is, and the server passes that name on. Attensira shows it only when it is one it recognises:
| Shown as | For |
| - | - |
| via Claude, via Claude Code | Claude (claude.ai, Desktop, mobile) and Claude Code |
| via ChatGPT | ChatGPT |
| via Cursor, via Codex, via VS Code | Those clients |
| via n8n | n8n, which counts as API use rather than as an assistant |
| via your assistant | A client signed in through OAuth, or one that named itself, under a name Attensira does not recognise |
| via the API | A REST call with an API key that did not say what made it |
A REST caller can name itself with the `X-Attensira-Client` header, for example `X-Attensira-Client: n8n`. Work you do yourself in the dashboard is never marked.
The mark is a label and nothing more. A client chooses what it calls itself, so the name never grants or limits anything: what a client can do is decided only by the access you approved or the key's scope.
***
## Any other MCP client
Any client that supports remote MCP servers can connect with these facts:
```text Connection details theme={null}
URL: https://mcp.attensira.com/mcp
Transport: Streamable HTTP (POST + SSE response stream). No stdio, no plain SSE transport.
Auth: OAuth 2.1 — discovery at /.well-known/oauth-protected-resource
or Authorization: Bearer atn_live_<32 hex>
```
Client authors: the full wire detail — endpoints, scopes, PKCE, registration, token lifetimes — is in the [OAuth reference](/mcp/oauth).
If the client speaks stdio only, run the bridge locally and point the client at it. Omit `--header` to let the bridge run OAuth for you:
```bash Shell theme={null}
npx -y mcp-remote https://mcp.attensira.com/mcp \
--header "Authorization: Bearer ${ATTENSIRA_API_KEY}"
```
To confirm the endpoint is reachable before you debug the client, request the unauthenticated health path:
```bash Shell theme={null}
curl -i https://mcp.attensira.com/healthz
```
Connected. Ask your assistant to call `get_account` — it answers without you supplying a workspace id, and its `workspaces` array is what every other tool's `project` takes. With one workspace it is also named as `project`; with several, `project` is `null` until you name one, which is expected rather than a fault.
If a client stays silent or shows no tools, see [Troubleshooting](/mcp/troubleshooting).
# OAuth for the MCP server
Source: https://docs.attensira.com/mcp/oauth
Discovery, scopes, PKCE and token lifetimes for connecting a client to the Attensira MCP server.
Most people never need this page. If you are connecting Claude, Claude Code, Cursor or VS Code, the client does all of this for you — go to [Connect a client](/mcp/connect) and click through the browser prompt.
This page is for the other case: you are writing an MCP client, reviewing Attensira before listing or approving it, or debugging a flow that stops somewhere in the middle and you need to know which document said what.
## Two roles, and only one of them is ours
| Host | Role | What it serves |
| - | - | - |
| `mcp.attensira.com` | Resource server | The MCP endpoint and its protected-resource metadata |
| `clerk.attensira.com` | Authorization server | Sign-in, consent, and every token |
The authorization server is **Clerk**, running under our own hostname. That is the same system that signs you in to the dashboard, so approving a client is the sign-in you already know.
The split is not cosmetic. The resource server never holds a signing key and never issues anything — it verifies what arrives and forwards it. Everything about *issuing* a token, and every rule that governs one, belongs to the authorization server.
This page describes the resource server's behaviour precisely, because that is ours. For the authorization server it describes the shape of the flow and points you at the discovery document for the specifics. Endpoint paths, registration limits, refresh-token format and lifetimes are the authorization server's to state, and a copy of them here would be a copy that goes stale.
## Discovery
Start with an unauthenticated request to the MCP endpoint. It answers `401` with a challenge that points at the metadata:
```bash Shell theme={null}
curl -i -X POST https://mcp.attensira.com/mcp
```
```http Response header theme={null}
WWW-Authenticate: Bearer realm="attensira", error="invalid_token", error_description="The access token is invalid or expired", resource_metadata="https://mcp.attensira.com/.well-known/oauth-protected-resource/mcp"
```
Two paths serve the protected-resource document (RFC 9728), with identical bodies — clients differ on which one they probe:
```text Protected-resource metadata theme={null}
GET https://mcp.attensira.com/.well-known/oauth-protected-resource
GET https://mcp.attensira.com/.well-known/oauth-protected-resource/mcp
```
```json Body theme={null}
{
"resource": "https://mcp.attensira.com/mcp",
"authorization_servers": ["https://clerk.attensira.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email", "offline_access", "user:org:read"],
"resource_documentation": "https://docs.attensira.com/mcp/overview"
}
```
`authorization_servers` names the issuer. **Read the endpoints from its own document rather than from this page** — that is what discovery is for, and it is the only copy that cannot be out of date:
```text Authorization-server metadata theme={null}
GET https://clerk.attensira.com/.well-known/oauth-authorization-server
```
It gives you `authorization_endpoint`, `token_endpoint`, `jwks_uri`, `registration_endpoint`, the supported grant types and the PKCE methods.
Until the authorization server answers, the `401` above omits `resource_metadata`. That is deliberate: a client that sees the pointer starts a flow, and pointing one at a server that is not there fails worse than a plain refusal. If you get a `401` with no pointer, OAuth is not available yet on that deployment — use an [API key](/account/api-keys).
## Scopes
There are deliberately no Attensira scopes. The scopes a token carries are the authorization server's own built-ins — `openid`, `profile`, `email`, `offline_access` and `user:org:read` — and none of them names an Attensira permission. A token names a person; what that person may do is a question the Attensira backend already answers from the verified subject, and a second permission system here would only be a second thing to disagree with it.
Two consequences follow. **Every OAuth connection is read and write.** Approving a client grants it your organisation, and there is no read-only consent — restrict a connected assistant by what you ask it to do, or by the tool allow-list your client offers, and use a read-only [API key](/account/api-keys) where you need a credential that cannot write. And **`offline_access` is the one scope that is not optional in practice**: access tokens are short-lived and refreshing is the only way a client stays connected, so a client that does not request it will ask you to sign in again within the hour.
`user:org:read` lets the authorization server stamp an organisation on the token when you have one. It is read when present and never required: a solo workspace has no organisation to stamp, and a connection is resolved from who you are either way.
## Authorization code with PKCE
The authorization code grant with PKCE (`S256`) is what to implement. Confirm the supported methods in the discovery document before you build against it.
```text Authorize request theme={null}
GET
?response_type=code
&client_id=
&redirect_uri=
&code_challenge=
&code_challenge_method=S256
&scope=openid%20profile%20email%20offline_access%20user:org:read
&resource=https%3A%2F%2Fmcp.attensira.com%2Fmcp
&state=
```
The user signs in and approves. The redirect back carries the code and your `state` echoed byte for byte; exchange the code at the `token_endpoint` with your `code_verifier`.
The `resource` parameter (RFC 8707) must be exactly `https://mcp.attensira.com/mcp`. It is what binds the token to this server, and the resource server checks it on every request — see [Tokens](#tokens).
Authorization codes are single use. Do not retry an exchange on a network error without first checking whether the original attempt succeeded: a replayed code is indistinguishable from a stolen one, and is treated as such.
## Registering a client
Clients register through dynamic client registration (RFC 7591). Client ID Metadata Documents are not offered: the authorization server's discovery document does not advertise `client_id_metadata_document_supported`, so register dynamically. The discovery document names the `registration_endpoint`; the limits and expiry that apply to a registration are the authorization server's and are stated there.
No client secrets are issued. Every MCP client is a public client, and a secret shipped inside one is not a secret.
Anyone can register a client and choose its display name. Read the name on the consent screen before approving, and treat an unfamiliar client as a thing to be sure about: every approved connection can add prompts, run automations and spend credits, because there is no read-only consent.
## Tokens
Access tokens are RS256-signed JWTs with `aud` set to `https://mcp.attensira.com/mcp`. The resource server verifies each one locally against the authorization server's published keys: signature, issuer, audience and expiry. Nothing is re-signed and nothing is rewritten before it reaches the API.
They are short-lived, and the client refreshes them in the background. Refresh eventually runs out, at which point the client asks you to sign in again — occasional, and the point of the design rather than a fault. Lifetimes are the authorization server's to set.
The access token names you by reference — the user, and the organisation when the authorization server has one for you — and carries no email, no name and no other personal data.
Because verification is local, the resource server never asks whether a token is still wanted. Revoking a connection stops the next refresh, but an access token already in a client's hands stays valid until it expires. If you need access cut off immediately, use an [API key](/account/api-keys) and revoke it — a key is checked on every request.
## Which workspace a token reaches
A token names your **organisation**, not one workspace. Every tool that works on a workspace takes an optional `project`, so one connection reaches every workspace the organisation owns — an agency running three domains connects once, not three times. Omit it and your only workspace is used; if there are several, name one.
`get_account` lists the ids in `workspaces`. A workspace id outside your organisation comes back as a workspace that does not exist, which is the same answer as one that never existed.
## Errors from the MCP endpoint
These are the resource server's, and they are exact.
| `error` | HTTP | Means |
| - | - | - |
| `invalid_token` | 401 | The access token is missing, malformed, expired, not for this resource, or not signed by a key the authorization server publishes |
The `401` is deliberately the same string for every cause. Telling a caller *which* one is true tells an attacker how close they are. It is the only refusal the MCP endpoint makes: there is no `403` and no scope gate, because a token that verifies may call any tool, and whether that customer is allowed the answer is decided by the backend — the one place that knows what an Attensira workspace is. The single exception is `get_ai_traffic_install`, which the backend refuses to a token because its response carries a signing secret; it says so, and the fix is an API key.
Errors raised during the flow itself — `invalid_request`, `invalid_client`, `invalid_grant`, `invalid_scope`, `invalid_target`, `access_denied` — come from the authorization server in the RFC 6749 envelope, and its documentation governs them.
## Static keys still work here
None of this replaces `atn_live_` keys. The MCP endpoint accepts an OAuth token or a static key, permanently, and headless callers such as n8n and CI should keep using keys — they cannot complete a browser sign-in. See [API keys](/account/api-keys).
The [REST API](/api/authentication) takes a static key only. OAuth tokens are for the MCP server.
# Attensira MCP server
Source: https://docs.attensira.com/mcp/overview
Query your Attensira workspace and act on it from inside your AI assistant, over Streamable HTTP with OAuth or a workspace-scoped API key.
The Attensira MCP server puts your visibility data inside the assistant you already work in. Instead of switching to the dashboard, reading a number, and carrying it back by hand, your assistant queries the workspace directly, then changes it — adding prompts, running automations — in the same conversation.
## What MCP gives you that the dashboard does not
The dashboard shows you numbers. MCP lets an assistant read those numbers and act on them without you leaving the work you are doing. That closes the loop between noticing something and changing something.
Concretely: while editing a pricing page, you can ask which prompts mention a competitor and not you, then add three new prompts covering the gap — one conversation, no tab switching. While reviewing a release, you can pull share of voice for the last 30 days, compare it against the previous window, and check whether the delta is statistically real before anyone writes it into a report.
The tools come in groups: find and measure (`search`, `get_analytics`, `deciding_sources`, `win_plan`, `get_prompt`, `get_page`, `query_evidence`), the receipts behind the numbers (`answer_history`, `answer_slice`, `citation_timeline`, `page_history`), track (`add_prompts`, `remove_prompt`, `add_competitor`, `remove_competitor`), inbox (`list_inbox`, `get_inbox_item`, `resolve_inbox_task`, `answer_inbox_question`, `approve_work_item`, `decline_work_item`, `approve_ask`, `decline_ask`), automations (`list_automations`, `get_automation`, `create_automation`, `run_automation`, `delete_automation`, `update_automation`), agent (`ask`, `get_session`), sessions (`list_sessions`, `cancel_session`, `delete_session`, `add_context`), account and settings (`get_account`, `get_brief`, `list_wins`, `get_costs`, `get_loop_health`, `get_ai_traffic_install`, `connect_github`, `update_settings`), memory (`record_observation`, `write_skill`), setting up a workspace (`start_workspace`, `confirm_setup`), goals (`get_goals`, `set_goal`), projects (`list_projects`, `steer_project`), deliverables (`list_deliverables`), reports (`get_report`), and markets (`get_markets`, `preview_markets`, `get_brand_family`).
The destructive ones are `remove_prompt`, `remove_competitor`, `delete_automation` and `delete_session`. The ones that spend credits are `add_prompts`, `run_automation` and `ask`, plus `add_context` with `run: true`, `answer_inbox_question` when the run that asked has finished, and the run that answers a `steer_project` post. Everything else is free. When a customer asks why nothing happened, call `get_loop_health` first. The [tool reference](/mcp/tools) has every argument and return shape.
## Your assistant can set up the business, too
A person does not need the dashboard to start. Connected over OAuth, an assistant can call `start_workspace` with the business's domain, show the person what the agents drafted — the profile, the rivals, the platforms, the goals and the plans — and save what they keep with `confirm_setup`. Nothing is charged from chat: `confirm_setup` returns a checkout link, the person pays in their own browser, and the agents start work once it reads `active`. See [Set up a business from your assistant](/mcp/recipes#set-up-a-business-from-your-assistant).
Work an assistant starts carries its name in the app — "via Claude", "via Cursor" — so the people on the account can tell what was asked from chat. A client Attensira does not recognise is shown as "via your assistant".
If what you are building is your own code rather than an assistant, the [REST API](/api/overview) exposes most of these capabilities as conventional HTTP endpoints. It takes an API key only — no OAuth — and those keys do not expire.
An assistant granted write access — by an API key minted as read and write, or by any OAuth connection, which always carries write — can delete prompts, competitors, automations and sessions, and can spend credits. Treat either credential as a workspace credential, not a read token.
## How authentication works
The server accepts two credentials, and it accepts both permanently. Which one you want depends on whether a human is present when the client starts.
| | OAuth | Static API key |
| - | - | - |
| Best for | Interactive AI clients — Claude, Claude Code, Cursor, VS Code, ChatGPT | Headless callers — n8n, CI, scheduled scripts |
| What you do | Paste the server URL and sign in through the browser | Mint a key and send `Authorization: Bearer atn_live_<32 hex>` |
| What sits on disk | A refreshable token the client manages. No secret you have to place | A long-lived key in a config file or secret store |
| Expiry | Access tokens are short-lived and refresh silently; you sign in again occasionally | Never, until you revoke it |
| How you take it away | Remove the server in the client | Revoke it in Settings → Your assistant, effective on the next request |
Prefer OAuth wherever a browser can open. There is no key to paste, so there is nothing to leak into a committed config file, and revoking one client leaves the others working. Use a key when nothing can complete a browser sign-in — that case is real and supported, which is why keys are not deprecated here. Full mechanics are in the [OAuth reference](/mcp/oauth); minting and scopes are in [API keys](/account/api-keys).
Either way, the credential names your organisation. Every tool that works on a workspace takes an optional `project`, so one connection reaches every workspace the organisation owns — an agency running three domains connects once, not three times. Omit it and the default is used: for a signed-in connection that is the organisation's only workspace, and for a static key it is the workspace the key was minted in. An organisation with several workspaces and a signed-in connection has no default, so tools ask you to name one. `get_account` lists the ids and answers without a workspace.
That is still a boundary. A workspace id outside your organisation comes back as a workspace that does not exist — the same answer as one that never existed, so naming an id you do not own tells you nothing.
An API key is displayed exactly once, because only a hash is stored; Attensira cannot re-display it, and a lost key is replaced rather than recovered.
## Where the server lives
The endpoint is `https://mcp.attensira.com/mcp`, and it speaks Streamable HTTP only. A client that speaks stdio alone cannot reach it directly and needs a bridge — see [Connect a client](/mcp/connect). `https://mcp.attensira.com/healthz` is unauthenticated and answers without a key, which makes it a useful reachability check when a client is silent about why it failed.
## Next steps
Copy-pasteable setup for Claude Code, Cursor, VS Code, Codex, ChatGPT, n8n and more.
Every tool, its arguments, and which ones cost credits.
Endpoints, scopes, PKCE and token lifetimes, for client authors and reviewers.
Worked prompts that get useful answers on the first try.
See which applications have access, and revoke one.
Minting, scopes, and the one-time reveal.
Something not working? [Troubleshooting](/mcp/troubleshooting) covers 401s, sign-ins that never return, missing tools, revoked credentials, empty results, and timeouts.
# MCP recipes
Source: https://docs.attensira.com/mcp/recipes
Task-shaped recipes for the Attensira MCP server — the call to make, the chain behind it, how to read the result, and what it cannot tell you.
Each recipe below is a real question someone asks on a Monday morning. Every one gives you the single call that mostly answers it, the longer chain when the single call is not enough, a table mapping what you see to what it means, and — the part that matters — what the numbers genuinely cannot tell you.
All calls assume you are connected to `https://mcp.attensira.com/mcp` — over OAuth or with a workspace API key; the recipes read the same either way. See [the tool reference](/mcp/tools) for parameters and the scope each tool needs, and [Connect a client](/mcp/connect) if you are not connected yet.
## Find out where you lost ground this week
Start with workspace-level rates over a short window, then drill into the prompts that moved.
```json theme={null}
{ "tool": "get_analytics", "arguments": { "area": "visibility", "days": 7 } }
```
**The chain**
Establishes whether anything moved at all at the workspace level. If every delta comes back `real: false`, stop — there is nothing to explain.
Ranks prompts by how badly you are doing on them, so the losses surface first rather than being averaged away.
The same call already splits the window by platform: one row each. A drop concentrated on one platform is a different problem from a drop across all of them. `get_analytics area=platform filter=chatgpt` opens one platform end to end, prompts included.
`get_prompt` says who was named and cited instead; `answer_history` returns the answers themselves, verbatim, which is the only place you find out *why* the model stopped naming you.
**How to read it**
| What you see | What it means |
| - | - |
| Delta `{real: false, value: null}` | No change you can prove. Not a decline. Do not report it as one. |
| Your share of voice flat, a competitor's rising | You did not lose ground — someone else gained. Different response. |
| Drop on one model, others flat | Likely an answer-surface change on that model, not a content problem. |
| `value: null` on a model row with `tracked: false` | That model was never queried. It is not a 0%. |
| Rate down but `n` is 7 or fewer | One or two bad readings can move this. Widen `days` before acting. |
**What this cannot tell you.** A seven-day window on Starter sampling gives you very few readings per prompt, and a rate built on single-digit `n` swings hard for reasons that have nothing to do with you. Nothing here tells you *why* a model changed its answer: model providers swap the underlying models behind a surface without notice, and a rewritten system prompt on their side looks identical to a content problem on yours. You are seeing what changed, not who changed it.
## See which sources the models trust in your category
Source share tells you which domains the models actually pull from when answering your prompts.
```json theme={null}
{ "tool": "get_analytics", "arguments": { "area": "source", "days": 90, "order": "best_first", "limit": 50 } }
```
**The chain**
A long window, because source share is a distribution and short windows make it lumpy. `best_first` puts the most-cited domains at the top.
Splits one domain's citations by platform and by day. Perplexity and ChatGPT lean on visibly different domain mixes, and a category-wide average hides that. `deciding_sources` ranks the same domains by influence over a decision rather than by count.
Shows where your own domain lands in the same window, so you can see whether you are in the mix at all.
**How to read it**
| What you see | What it means |
| - | - |
| One domain above roughly a fifth of all citations | Models treat it as the reference for your category. Being absent from it costs you. |
| A long flat tail of many small domains | No consensus source. Coverage breadth matters more than any single placement. |
| High source share, low `position` index | Cited often and cited early in the list. The strongest signal available here. |
| Your domain absent entirely | You were not cited in this window. Not proof you are uncitable. |
**What this cannot tell you.** Source share is citations to a domain over all citations in the window — it is not traffic, authority, or influence on the answer text. A model can name your brand in prose while citing a competitor's blog, and a model can cite a page it disagreed with. The list also reflects your prompts, not the category: if your tracked prompts skew to one topic, the source mix skews with them. Widen the prompt set before calling it a picture of the category.
## Check whether a competitor is gaining
```json theme={null}
{ "tool": "get_analytics", "arguments": { "area": "competitor", "days": 30 } }
```
**The chain**
Gives each tracked competitor's share of voice on the same denominator as yours.
Your own rate over the same window, so you can compare movement rather than levels.
Finds the specific prompts where the gap lives. Competitors rarely gain evenly.
Read the answers. What the model says about them is the actual competitive intelligence.
**How to read it**
| What you see | What it means |
| - | - |
| Their rate up, yours flat, both deltas `real: true` | They gained. You did not lose. The market grew around you. |
| Both rates up | The models are naming more brands per answer, not reshuffling ranks. |
| Their rate up, yours down | The closest thing to genuine displacement — and worth reading answers for. |
| Rates summing well past 100% across brands | Expected. Answers name several brands, so these never sum to 1. |
**What this cannot tell you.** These are not shares of a fixed pie. Competitor share of voice uses the same denominator as yours, so it measures how often a brand appears in *your* prompts' answers — nothing about their overall market presence, spend, or growth. A competitor you have not registered with `add_competitor` is invisible here and will read as absent, which is not the same as not appearing. There is no sentiment measurement, so a mention that trashes them counts exactly like a recommendation.
## Turn a losing prompt into a plan
The one prompt you keep losing is the most actionable object in the product.
```json theme={null}
{ "tool": "get_prompt", "arguments": { "prompt": "best project management tool for agencies", "days": 90 } }
```
**The chain**
Returns per-model visibility rates and, in the same call, the competitors seen in this prompt's answers and the sources those answers cited — no need to call get\_analytics separately for either.
Tells you what already works, which is a better template than guessing.
The answers themselves, verbatim. Read what the model actually said before deciding what to change; a rate is a summary of these rows, not a substitute for them.
Free, and already ordered: what is deciding the answer today on each platform, the one next move per platform with the sentence of evidence behind it, and a credits estimate.
Costs credits, and earns them here: the agent reasons across the slices you just pulled and drafts a plan. Poll `get_session` if it returns `status: "running"`.
**How to read it**
| What you see | What it means |
| - | - |
| Competitors listed in `get_prompt`, you absent, sources pointing to third-party lists | A placement problem. You need to be on those lists. |
| Your visibility rate low but not zero | The model names you sometimes. Consistency, not existence, is the gap. |
| A cited source you do not own, with a high `n` | The model has a go-to reference for this prompt that is not you. |
| Share of voice 0 with `n` above 20 | A real, measured absence worth working on. |
**What this cannot tell you.** Nothing here predicts that a change will work. There is no attribution path from a page edit to a later mention — you can only change something and re-measure after enough calendar days have passed to build a new `n`. Sampling is at most once per prompt per model per day, so a plan executed today shows its earliest possible signal tomorrow and a trustworthy one much later. Answers also vary run to run at the model's discretion; a single collected answer is an anecdote.
## Set up a weekly report
```json theme={null}
{ "tool": "create_automation", "arguments": {
"name": "Monday visibility digest",
"instruction": "Summarise the last 7 days: workspace mention and citation rates with deltas, the five worst prompts, per-model split, and any competitor whose rate moved with real:true. Ignore deltas where real is false.",
"trigger": "weekly",
"timezone": "Europe/London",
"run_at_minute": 540
} }
```
**The chain**
Check what already exists. Two automations doing the same job bill twice.
Free to create. Write the instruction to name the exact areas and windows you want, and to say explicitly that unproven deltas should be ignored. Pass `tools` if you want to restrict which tools this automation may call, and `run_at_minute` with a `timezone` to pin when it fires.
Test it once now rather than waiting a week. The key prevents a retry from producing a second billed run.
Read the run history to confirm it produced what you expected before trusting it unattended.
**How to read it**
| What you see | What it means |
| - | - |
| Digest reports every delta as a change | Your instruction did not exclude `real: false`. Rewrite it. |
| A week with no findings | Normal. Most weeks contain no statistically real movement. |
| Numbers differing from the dashboard | Check the window: 7 days against a 30-day default is a different slice, not a bug. |
| A run missing from the history | The scheduled run did not execute. Re-run manually with a fresh key. |
**What this cannot tell you.** A weekly cadence over a daily sampling floor means each report rests on at most seven readings per prompt per model — three times that on Growth, Business and Agency, since sampling depth is three there. That is thin for week-over-week comparison, and a digest that dutifully reports movement every week is reporting noise.
## Audit which pages actually get cited
```json theme={null}
{ "tool": "get_analytics", "arguments": { "area": "page", "days": 90, "order": "best_first", "limit": 100 } }
```
**The chain**
Ranks your own URLs by citation activity. Long window, because page-level citations are sparse.
Shows which prompts and which models each page earns citations from.
Shows AI crawler activity against your site — which pages were fetched at all.
**How to read it**
| What you see | What it means |
| - | - |
| Page cited often across many prompts | A hub. Keep it current; it carries disproportionate weight. |
| Page crawled but never cited | Reachable and not chosen. A content problem, not an access one. |
| Page never crawled and never cited | Start with access — nothing can cite a page it has not fetched. |
| Citations concentrated on one model | That surface favours your format. Others may need different structure. |
**What this cannot tell you.** Crawlers are not models: a crawler fetches pages, a model answers prompts, and a fetch is not a citation or a plan to make one. Crawler data also has a hard technical limit — AI crawlers do not execute JavaScript, so a client-side beacon, however written, has never seen one and never will; by the time the script runs, the only visitors left are humans with browsers. Use the install instructions the product generates at Settings for server-side collection. Finally, `position` is a mean index within a citation list per source domain — it is not a rank against competitors and cannot be read as one.
## Hand several pieces of work to the agents
Your assistant does not have to do the work itself, or ask for it one piece at a time. Send the work to `ask` as lines, one piece of work per line, and the Chief of staff hands each line to the project whose crew does it.
```json theme={null}
{ "tool": "ask", "arguments": { "question": "Hand these off:\nWrite an article comparing our plans with the two biggest alternatives\nFix the meta descriptions on the pricing pages\n#outreach pitch us to the three directories that decide \"best crm for startups\"" } }
```
**The chain**
Up to 20 lines, each in your own words and specific enough to start on. A line that names a project with its `#slug` goes there; the rest are routed by what each project is for. See [Delegation](/agent/delegation) for the rules.
The answer says, in a sentence or two, what went where and what happens next. The same session in the dashboard shows the **Handed off** card, one row per line.
The session is waiting on a person: an approval of the hand-off (in Ask mode), or a choice of project for lines Attensira was not sure about. Both are answered in that session in the dashboard; `get_session` reads `running` until then.
What the crews file as they work: drafts, pull requests and pitches waiting for approval. A task handed off is not a task done.
**How to read it**
| What you see | What it means |
| - | - |
| A line handed to a project | Filed there as a task of its own, queued for the project's crew. The work happens in the task, not in this session. |
| A line waiting on a choice of project | Attensira was less than 60% sure, or the line named a project it could not find. Someone picks in the dashboard, with one click. |
| The work done in the session itself | The workspace has no projects taking work, so the Chief of staff does it there instead, or offers to make it a project. |
| A line not handed off | Filing it failed, with the reason; for example, the balance is spent. Send that line again. |
**What this cannot tell you.** Routing reads each project's name and focus, not the work it will produce, so a confident route is a statement about where the line belongs, not about how the work will turn out. Over MCP you can neither approve the hand-off nor pick a project; a person does that in the dashboard. And nothing here promises an outcome: Attensira guarantees the volume of work, not visibility. Measure what the work changed with [before and after](/measure/before-and-after), once it has shipped.
## Set up a business from your assistant
A person can start from chat: "set up Attensira for example.com". The agents draft the business from its site; the person decides what to keep; the person pays in their own browser. Nothing in this chain can charge them.
```json theme={null}
{ "tool": "start_workspace", "arguments": { "domain": "example.com" } }
```
**The chain**
Creates the workspace, or returns the one already started for that domain. While `status` is `drafting` the site is still being read: call it again in a little while. It never creates a second workspace.
Walk them through `profile.facts` (each with the page it was read from), `competitor_suggestions`, `platform_suggestions`, `proposed_goals`, the `plans` and the `budget` range. When `status` is `needs_input`, ask the `question` on each fact that has no value. Ask; do not decide for them.
Pass the `workspace_id`, the `plan` id they chose, and the competitors, platforms, goals, budget and profile corrections they kept. It returns `checkout_url`.
Send the `checkout_url` and read them `budget_consent.line`, the top-up rule they agree to by paying, as written. They pay in their browser. If they come back later, call `confirm_setup` again for a fresh link.
Same call, same domain. `active` means the plan is paid and the first readings are being taken; `first_readings_ready` means there is enough to read a brief from.
`get_goals` shows what the agents are aiming at and `list_projects` the crews doing the work. `get_report` has something to return once the first report is published.
**How to read it**
| What you see | What it means |
| - | - |
| `status: drafting` | The site is still being read. Poll; do not invent the profile yourself. |
| `status: needs_input` with a `reason` | The site did not say enough. The questions in `profile.facts` are what to ask. |
| A fact with a `value` and a `source_url` | Read off that page. Show it, so the person can correct it. |
| `status: awaiting_payment` | The choices are saved and the checkout is theirs to finish. Nothing is charged until they pay. |
| `confirm_setup` sends them to the billing page | They already have a card on file, so they confirm the plan there, in person. |
| `start_workspace` refuses a second domain | An account holds one workspace waiting for payment at a time. Finish that one first. |
**What this cannot tell you.** The draft is the agents' reading of the site and the answers, and it can be wrong: a rival they suggest may not be one, and a fact may be out of date. The person's corrections are what make it right. Until the first readings land there are no numbers at all, so do not describe the business's visibility before `first_readings_ready`.
## Steer a project
Projects are where the crews work. A person steers one in plain words — "focus on the pricing comparison pages this month", "stop pitching review sites" — and the crew replans. Steering is direction, never approval.
```json theme={null}
{ "tool": "list_projects", "arguments": {} }
```
**The chain**
Every project, with its crew, its goal, `needs_you` and `last_activity_at`. Pick the one the person means. Its `id` is the project; `project`, if you pass one, is the workspace.
The project's `direction` so far and its latest `feed`, so you do not repeat what it was already told.
The person's words, verbatim, at most 4,000 characters. Pass an `idempotency_key` so a retry does not post twice. Tell them the `ack`.
The crew's reply and any plan change land in the feed as their own posts. Anything that needs a yes lands in the Inbox: `list_inbox`.
**How to read it**
| What you see | What it means |
| - | - |
| `plan_change: null` on the reply | Always. A steer changes no plan by itself; the crew's run decides, and posts what it changed. |
| `run_id: null` with `reply_error` | The post is saved, but the crew's reply could not start. The error says why. |
| `replayed: true` | That key was already used. Nothing was posted again. |
| `needs_you` above zero on the project | Work is waiting on a person. Steering does not clear it; approving or declining does. |
**What this cannot tell you.** A steer is not an approval and not a setting. "Approve everything and publish" posted as direction is kept as words in the feed; what the crew may ship without asking is set on the approval settings, by the person. The crew reads the direction on its next run, so nothing changes on the site the moment you post.
## What did my team ship this week, and what is waiting on me?
The Monday question. What the crews delivered, what it proved, and the decisions only a person can take.
```json theme={null}
{ "tool": "list_deliverables", "arguments": {} }
```
**The chain**
Newest first. Keep the rows whose `shipped_at` (or `created_at`, for work not shipped yet) falls in the week. Each says where it lives in `location` and where it stands in `status_label`. Page with `next_cursor` until you are past the week.
The three decisions to take first, and how many are open in all. `get_inbox_item` on a ref reads it in full.
Platforms that named or linked the brand for the first time on a prompt. Quote each row's `sentence`.
On Mondays the weekly note, with shipped work and early signals in the order the email reads them.
**How to read it**
| What you see | What it means |
| - | - |
| `result: null` | Nothing to measure yet: not shipped, or nothing tracked names it. Not a failure. |
| `result` with `verdict: null` | Shipped, and the re-read that will measure it has not come back. Its `label` says when. |
| A win's `quote: null` | No excerpt of the answer passed the honesty check. The win still stands; quote the `sentence`. |
| An empty triage `top` | Nothing is waiting on a person. The crews are not blocked on you. |
**What this cannot tell you.** Shipping is not moving. A deliverable says what was done; only a re-read says whether an answer changed afterwards, and a win is a first since readings began, not proof that a given piece of work caused it. Report what shipped, and report movement only where a re-read proved it.
## Where to go next
Before acting on any of these numbers, read [Reading the numbers](/measure/reading-the-numbers) for how `null`, zero, small `n`, and unproven deltas differ. Full parameter detail for every tool is in [the tool reference](/mcp/tools).
# MCP tool reference
Source: https://docs.attensira.com/mcp/tools
Every Attensira MCP tool — parameters, return shapes, which credential each needs, and which ones spend credits or delete things.
The Attensira MCP server serves its tools at `https://mcp.attensira.com/mcp` over Streamable HTTP. Every tool except the two that set up a new workspace takes an optional `project`; omit it and your credential's own workspace is used. Each tool below says whether it is read-only, whether it spends credits (`add_context` does only with `run: true`, and `answer_inbox_question` only when it restarts a finished run), and whether it deletes data permanently. This page lists every tool with its parameters, defaults and return shape, grouped the way the server lists them.
The agents do the SEO work; the tools are how you, or the assistant you ask, steer it: read what happened, set what to aim for, answer what is waiting on a person, approve or decline what the agents propose. A tool never approves anything on its own reading of a row. Approvals and settings change on a person's instruction.
Every tool has a twin in the [REST API](/api/endpoints) with the same payload, and each says which endpoint below, except the markets reads, which say **MCP-only**. Use MCP when an assistant is doing the work; use REST when your own code is.
[Projects](/agent/projects) are read with `list_projects` and steered with `steer_project`; [Projects over MCP](/agent/projects#projects-automations-and-the-api) describes both. Creating a project and changing its settings are in the dashboard only. A project's runs and automations are ordinary sessions and automations, so `list_sessions`, `get_session`, `list_automations` and `get_automation` include them.
## Words that collide: `project` and `id`
On every tool, `project` is the **workspace**: one domain, with its own prompts, competitors and plan. It has meant that since the first tool shipped, and renaming it would break every client already calling.
A project in the product's sense, an area of work with its own crew of agents, is a **work area** on the wire. `list_projects` and `steer_project` take its id as `id`, and every payload that points at one, a goal or a deliverable, carries it as `work_area_id`. Passing a project's id as `project` is refused with a message naming the mistake.
## Which credential each tool needs
A **workspace API key** is minted as *read* or *read and write*. Read tools work with either; the tools in the left column need a read-and-write key. Calling one with a read key returns `403` with `type: "forbidden"` and the challenge `WWW-Authenticate: Bearer error="insufficient_scope", scope="write"` — the credential is valid, it simply may not do this. Scope is fixed when the key is minted and cannot be widened: mint a read-and-write key and swap it in.
An **OAuth connection** always carries read and write. Approving a client grants it your organisation, and there is no read-only consent — the scopes in the token are the authorization server's own (`openid`, `profile`, `email`, `offline_access`, `user:org:read`), none of which names an Attensira permission. Restrict a connected assistant by what you ask it to do, or by the tool allow-list your client offers, not by scope.
| Needs write | Works with read |
| - | - |
| `add_prompts`, `remove_prompt`, `add_competitor`, `remove_competitor`, `resolve_inbox_task`, `approve_work_item`, `decline_work_item`, `answer_inbox_question`, `approve_ask`, `decline_ask`, `create_automation`, `run_automation`, `delete_automation`, `update_automation`, `ask`, `cancel_session`, `delete_session`, `add_context`, `connect_github`, `update_settings`, `record_observation`, `write_skill`, `start_workspace`, `confirm_setup`, `set_goal`, `steer_project` | `search`, `get_analytics`, `deciding_sources`, `win_plan`, `get_prompt`, `get_page`, `query_evidence`, `list_inbox`, `get_inbox_item`, `list_automations`, `get_automation`, `get_session`, `list_sessions`, `get_account`, `get_ai_traffic_install`, `get_brief`, `list_wins`, `get_costs`, `get_loop_health`, `answer_history`, `answer_slice`, `citation_timeline`, `page_history`, `get_goals`, `list_projects`, `list_deliverables`, `get_report`, `get_markets`, `preview_markets`, `get_brand_family` |
`get_account` returns the scopes in force, so call it first from a new integration rather than discovering the gap on a failed write.
One tool is **key-only**: `get_ai_traffic_install` hands back your workspace's signing secret, and a signed-in connection is refused there on purpose — a consented OAuth token must never be able to read one credential out and turn it into another. Call it with an API key.
## What your assistant does shows its name
Work an assistant starts is labelled with it in the app. A proposal that came out of an `ask` from Claude reads "via Claude" in the Inbox, and so does the work a project's crew does in answer to a `steer_project` post sent from Claude. The MCP server passes on the name your client reports for itself; Attensira shows it only when it is one it recognises (Claude, Claude Code, ChatGPT, Cursor, Codex, VS Code), and otherwise writes "via your assistant" rather than a name it cannot vouch for. The label is display only. It grants nothing and changes nothing about what the call may do.
## Before you start: what costs money and what destroys data
These tools spend credits or destroy data. Nothing else on this page produces a bill or erases history.
| Tool | Effect | Why |
| - | - | - |
| `add_prompts` | Costs credits | Each new prompt is read across your tracked platforms from the next run onward |
| `run_automation` | Costs credits | Triggers an automation immediately instead of waiting for its schedule |
| `ask` | Costs credits | Runs the agent on an agent model |
| `add_context` with `run: true` | Costs credits | Queues the session again to act on the new context now |
| `answer_inbox_question` | Costs credits when the run that asked had finished | A finished, failed or suspended run starts a new turn on the answer. A run still going reads it without a new turn |
| `steer_project` | The reply costs credits | Posting is free; the project's crew answers the post with an ordinary agent run |
| `remove_prompt` | Destructive | Stops tracking a prompt; its history is no longer collected |
| `remove_competitor` | Destructive | Stops scoring a rival; past readings keep what they recorded |
| `delete_automation` | Destructive | Removes the automation and its schedule. Past runs are kept as sessions |
| `delete_session` | Destructive | Deletes a finished session and its trace |
`start_workspace` and `confirm_setup` never charge anything. `confirm_setup` hands back a checkout link, and the person pays in their own browser.
`run_automation` accepts an `idempotency_key`. Pass one on every call. It maps to the `Idempotency-Key` header upstream, so a retried or duplicated call does not produce a second billed run.
The `get_analytics` `area` values for a topic rollup and a per-surface rollup are `topic` and `platform`. A **platform** is a stable identifier for an AI answer surface (ChatGPT, Perplexity, Google AI Overviews, Claude, Gemini) that survives the underlying model being replaced, so two windows a year apart stay comparable on the same key. The older wire words `group` and `engine` are no longer accepted.
## Reading what comes back
Every rate is `{value, n}` and every delta is `{value, real}`. A `value` of `null` means not measured; `0` with `n > 0` is a measured zero. Per-platform metrics live in a `platforms` map keyed by platform, and each entry carries `{tracked, readable}`: `tracked: false` means your plan does not include that platform, so it was never queried, and `readable: false` on a tracked platform means nothing came back in the window that could be scored. Treating either as 0% invents a failure that did not happen.
Every ranked list carries `id` and `label` on each row, so the row can be handed straight to the detail tool that takes the id. For the full treatment of null against zero, small `n`, and when a delta is worth acting on, read [Reading the numbers](/measure/reading-the-numbers).
## Find and measure
Ask these tools the questions you would ask an analyst: "which prompts am I losing on Perplexity?", "what changed since last week?", "which sources decided the answers?", "does anyone cite our pricing page?".
### search
Read-only. No credits. REST: `GET /v1/search`.
One search across the workspace's prompts, pages, automations and agent sessions. Use it to locate something by wording when you do not have an id — a prompt that sounds like the one you have in mind, the page you half remember the title of.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `query` | string | — | Required |
| `limit` | integer | 20 | Maximum 50 |
| `offset` | integer | 0 | For paging through results |
Returns `total`, `has_more` and `hits`. Each hit carries `type` (`prompt`, `page`, `automation` or `session`), `id`, `title` and, where there is one, a `snippet`. A hit whose label starts with the query ranks above one that merely contains it; at equal footing prompts come before pages, then automations, then sessions. **Every string on a hit is capped at 400 characters**, so a complete model answer can never come back through `search` — for the answers themselves, use `answer_history` or `answer_slice`.
Page on `has_more`. `total` counts what matched up to the session-search ceiling, so it is a floor rather than a grand total.
### get\_analytics
Read-only. No credits. REST: `GET /v1/analytics/{area}`.
The main measurement tool. One call returns a ranked slice of your tracking data for a window.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `area` | enum | — | Required. One of `visibility`, `prompt`, `page`, `source`, `competitor`, `topic`, `platform`, `bot` |
| `days` | integer | 30 | 1–365; out of range is clamped |
| `order` | enum | `worst_first` | Or `best_first`. Rows nothing was measured for sort last under either |
| `limit` | integer | 20 | Maximum 100 |
| `filter` | string | — | Case-insensitive substring on row labels. For `area: "platform"` it names the platform and is required |
| `area` | One row per | Answers |
| - | - | - |
| `visibility` | tracked platform | Share of voice and citation rate per platform, with the workspace's own rate alongside — the orienting call |
| `prompt` | tracked prompt | Which prompts you win and lose, with topic, country and the per-platform split |
| `page` | page of yours | Which of your pages get cited, as a rate on every answer sampled |
| `source` | external domain | Which domains the models cite in your category, as a share of every citation |
| `competitor` | rival | Workspace-wide share of voice per competitor, with the id `remove_competitor` takes |
| `topic` | prompt topic | Per-topic rollup; prompts filed under no topic are one row with id `no_topic` |
| `platform` | the platform named in `filter` | That platform end to end, with its prompts listed under the envelope |
| `bot` | AI system that fetched your site | Reads, pages reached, purpose mix, when it last came |
The envelope is `{area, days, window, order, filter, rows, total_rows, truncated}` plus the area's own headline numbers. `total_rows` is how many rows survived the filter and `truncated` whether `limit` cut them, so a short list can be told from a cut one. Every row carries `id`, `label`, the rates that apply to the area and, where the area has one, a `platforms` map.
```json Response — area: prompt theme={null}
{
"area": "prompt", "days": 30, "order": "worst_first", "total_rows": 12, "truncated": true,
"visibility": { "value": 0.3, "n": 20 },
"rows": [
{
"id": "prm_8f21", "label": "best crm for small teams",
"topic": "buying intent", "country": "US",
"visibility": { "value": 0.12, "n": 25 }, "citation": { "value": 0.04, "n": 25 },
"position": null, "delta": { "value": null, "real": false },
"platforms": {
"chatgpt": { "tracked": true, "readable": true, "rate": { "value": 0.2, "n": 15 }, "delta": { "value": null, "real": false } },
"perplexity": { "tracked": true, "readable": false, "rate": { "value": null, "n": 0 } }
},
"answers_read": 25, "failures": 1
}
]
}
```
`area: "bot"` rows carry `visits`, `pages`, `purpose_mix`, `last_seen` (`null` when it never came) and `agents` instead of the rate fields, and answer with a `note` when no edge collector has ever reported — zero rows there means uninstrumented, not uncrawled.
`area: "visibility"` returns share of voice per platform — the fraction of runs on that platform that named you, not a share of a category that sums to 100%. Competitor rates (`area: "competitor"`) use the same denominator as yours, so they do not sum to 1 either.
### deciding\_sources
Read-only. No credits. REST: `GET /v1/deciding-sources`.
Which pages and domains actually decided the AI answers — the listicles, reviews and docs the models leaned on — whether you already appear on each, and the next move for each. Unlike `get_analytics` with `area: "source"`, which ranks by citations, this ranks by influence over a decision. See [Deciding sources](/measure/deciding-sources).
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `prompt` | string | — | Optional. One tracked prompt, by id or exact text. Omit for every prompt |
| `days` | integer | 30 | 1–365 |
Returns `groups`, most influential first, one per prompt × platform × surface. Each group carries a `label` in words, `n` (successful answers read), `present` (whether any source in the top ten is yours), a `recommendation`, and `sources` ranked by weight: `domain`, `url` (the exact page when one carried it, else `null`), `citations`, `share`, `present`, and `action` — the next move in words, such as "get listed" or "fix your own page".
### win\_plan
Read-only. No credits. REST: `GET /v1/win-plan`.
The plan for winning one tracked prompt: what is deciding the answer today, what to change, and in what order. Ask for a prompt the workspace is losing — `deciding_sources` says which those are.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `prompt` | string | — | Required. Prompt id or exact text |
| `days` | integer | 30 | 1–365 |
Returns `prompt`, `n`, `evidence` (`measured`, `published` or `hypothesis`), `steps` in order — each with a `title`, a `why` sentence built from the readings, a `target` page or domain (or `null` when it is your own site generally), the `platform` it acts on and a `credits_estimate` — plus `rereads_at` and `credits_estimate` for the whole plan. `rereads_at` is the day the next booked re-read for that prompt is actually read (see [Before and after](/measure/before-and-after)). Nothing here claims movement: the footer uses the no-change wording until the workspace has a re-read that came back moved.
### get\_prompt
Read-only. No credits. REST: `GET /v1/prompts/{prompt}`.
Everything recorded for one prompt: where it is filed, its rate per platform, and who was named and cited instead.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `prompt` | string | — | Accepts either a prompt id **or** the prompt's exact text |
| `days` | integer | 30 | Window for the returned rates |
Returns `id`, `text`, `topic` (`null` when unfiled), `country` (`null` for the workspace default), `visibility`, `citation`, `delta`, `position`, `answers_read`, `failures`, a `platforms` map, `competitors` (each with `name` and a `rate` on this prompt's own answers), `sources` (each with `url`, `domain` and a `rate`), and `why` — the standing in sentences, each built from a number on the same response. Passing exact text saves a `search` round-trip when you already know the wording; inexact text will not match.
### get\_page
Read-only. No credits. REST: `GET /v1/pages`.
How one page performs in AI answers: its citation rate on every answer sampled, per platform, and the prompts whose answers cited it. For whether AI systems are fetching the site at all — as opposed to citing it — call `get_analytics` with `area: "bot"`; a model can only cite a page it has fetched, so that is worth checking before rewriting content.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `url` | string | — | Required. Absolute URL, exactly as it appears on the site |
| `days` | integer | 30 | Window |
Returns `url`, `title` (`null` when it could not be read), `citations` and `citation` as a rate on `total_answers`, a `platforms` map whose entries carry the page's citation rate on that platform's own answers, `prompts` — each with `id`, `text`, `citations` and a `rate` on that prompt's answers — and `in_sitemap`. The `delta` is `{value: null, real: false}`: the page fold has no earlier window to compare against, and the contract's word for that is "no basis", not a number.
### query\_evidence
Read-only. No credits. REST: `POST /v1/evidence/query`.
Your own data question as a table: one metric, grouped by up to two dimensions, filtered, over a window. "Share of voice by engine and week", "Google clicks by page", "wrong claims by topic". It reads the same stored days and folds them the same way as the dashboard boards, so the numbers match what the dashboard shows for the same window. Use `get_analytics` for the ready-made boards and `query_evidence` when the cut you need is not one of them.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `metric` | enum | — | Required. See the metric table below |
| `group_by` | string\[] | — | At most two of `day`, `week`, `topic`, `prompt`, `engine`, `surface`, `competitor`, `domain`, `url`, `page`, `intent`, `stage`, `persona`, `state`, `keyword`, `country`, `property`. Not `day` and `week` together. Omit for one total |
| `days` | integer | 30 | 1–365, ending today. Ignored when `from` and `to` are given |
| `from`, `to` | string | — | First and last day, `YYYY-MM-DD`, inclusive. Both or neither; the span is at most 365 days |
| `topic_ids`, `prompt_ids` | string\[] | — | At most 200 ids each. An id the workspace does not have is refused |
| `engines` | string\[] | — | Platforms: `chatgpt`, `perplexity`, `google_ai`, `claude`, `gemini` |
| `surface` | enum | — | `consumer` or `api` |
| `country` | string | — | One market, ISO 3166-1 alpha-2 |
| `markets` | string\[] | — | Several markets, e.g. `["US","DE"]`. Give `country` or `markets`, not both |
| `property_ids` | string\[] | — | Parts of the brand family, with `group_by: ["property"]` only |
| `competitor` | string | — | A tracked rival's domain |
| `intent` | string | — | `informational`, `comparison`, `commercial` or `transactional` |
| `stage` | string | — | `awareness`, `consideration`, `decision` or `retention` |
| `state` | enum | — | `won`, `contested`, `lost`, `absent` or `unmeasured` |
| `domain` | string | — | A cited domain |
| `text_contains` | string | — | A phrase in the prompt's text, case-insensitive, at most 80 characters |
| `limit` | integer | 50 | 1–200 rows |
| Metric | Value | Can be grouped by |
| - | - | - |
| `share_of_voice` | Share of answers that named you, 0 to 1 | time, prompt fields, `engine`, `surface`, `country`, `property` |
| `citation_rate` | Share of answers that cited your domain, 0 to 1 | time, prompt fields, `engine`, `surface`, `country` |
| `position` | Your average place in the answer's brand order | time, prompt fields, `engine`, `surface`, `country` |
| `n` | Successful answers read, a count | time, prompt fields, `engine`, `surface`, `country` |
| `source_share` | A cited domain's or page's share of every citation | time, prompt fields, `engine`, `surface`, `domain`, `url`, `country`. Needs `domain` or `url` grouped, or the `domain` filter |
| `deciding_weight` | A domain's weight in the [deciding sources](/measure/deciding-sources) | time, prompt fields, `engine`, `surface`, `domain`. Needs `domain`. By day or week, at most 90 days |
| `competitor_share` | Share of answers that named a rival, 0 to 1 | time, prompt fields, `engine`, `surface`, `competitor`, `country`. Needs `competitor` grouped or filtered |
| `competitor_position` | A rival's average place in the brand order | As `competitor_share` |
| `prompt_state_count` | Prompts in each state, a count | prompt fields only |
| `wrong_claims` | Claims about you that a confirmed fact contradicts, a count | time, prompt fields, `engine`, `surface` |
| `gsc_clicks`, `gsc_impressions`, `gsc_position` | Search Console clicks, impressions, average position | `day`, `week`, `page`, `keyword` |
| `serp_position`, `ai_overview_presence` | Your Google rank for a tracked search; share of days with an AI Overview | `day`, `week`, `keyword`, `page` |
| `ai_fetches`, `ai_referred_visits` | AI-crawler fetches of a page; visits an AI assistant sent | `page` |
"Time" is `day` or `week`; "prompt fields" are `topic`, `prompt`, `intent`, `stage`, `persona` and `state`. `page` is one of your own pages as Google and your traffic record it; `url` is a cited page, anybody's. The Google metrics are explained in [Google search](/measure/google-search).
A question with no honest answer is refused before anything is read. The tool returns an error (status `400`) whose message names the problem and says what to ask instead:
| Refused because | The message says |
| - | - |
| A metric or dimension outside the lists above | The valid ones |
| The metric cannot be grouped that way | What it can be grouped by |
| The metric does not take that filter | What it can be filtered by. The filter is refused, never ignored: ignored, it would return unfiltered numbers under a question that says they were filtered |
| A share of one rival, domain or page, without saying which | The dimension to group by or the filter to set |
| `group_by` names more than two dimensions, one twice, or both `day` and `week` | The rule it broke |
| A malformed or out-of-range value: a day that is not `YYYY-MM-DD`, a `from` after `to`, a span over 365 days, a `text_contains` over 80 characters, an unknown engine, intent, stage or state | The valid form or values |
| A topic, prompt or brand property id this workspace does not have | The id |
A question too broad to answer in time is refused with status `422` and a message that says what to narrow, never answered with whatever rows arrived first.
```json Response — metric: share_of_voice, group_by: ["engine"], days: 30 theme={null}
{
"metric": "share_of_voice",
"group_by": ["engine"],
"window": { "from": "2026-09-03", "to": "2026-10-02", "days": 30 },
"day_basis": "slot_day",
"unit": "share",
"rows": [
{ "key": { "engine": "chatgpt" }, "labels": { "engine": "ChatGPT" }, "value": 0.31, "n": 84,
"ref": { "collection": "prompts", "ids": ["prm_8f21", "prm_1c07"] } },
{ "key": { "engine": "gemini" }, "labels": { "engine": "Gemini" }, "value": null, "n": 0 }
],
"gaps": [{ "key": "engine=gemini", "reason": "no_readings" }],
"truncated": false
}
```
* `value` is in the table's `unit`: `share` (0 to 1), `position`, `count` or `weight`. `null` means unmeasured, and a gap names why; `0` is a measured zero.
* `n` is on every row: successful answers for an answer rate, placed answers for a position, citations for a source share, impressions for Search Console, readings for rank tracking, the count itself for a count. Read it before the value.
* `labels` gives the readable name where a key is an id: a topic's name, a prompt's text.
* `ref` names the records the row was counted from, at most five ids. For answer metrics they are prompt ids, which `answer_history` opens.
* `gaps` lists everything the answer could not measure, as `{key, reason}`: a span of days before your first tracked prompt (`before_first_reading`), a source that is not connected, days not recorded yet, a row with no value. An empty list is the claim that nothing is missing.
* `day_basis` says which calendar the day labels count in: `slot_day`, `search_console` or `utc`. See [Google search](/measure/google-search#day-basis).
* `truncated` is `true` when there were more rows than `limit`. Without a time dimension the strongest rows are kept; with one, the newest whole days or weeks.
Grouping by `topic` uses the topic each prompt is filed under now, so moving a prompt regroups its whole history.
## The receipts
The numbers above are folds over stored answers. These tools hand back the rows themselves, verbatim, which is what `record_observation` cites and what a person asks for when a number looks wrong. Their REST twins live under `/v1/memory/`.
### answer\_history
Read-only. No credits. REST: `GET /v1/memory/answers/history`.
Every sampled answer to one tracked prompt, verbatim and newest first, with citations and reading ids — the receipts behind `get_prompt`.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `prompt` | string | — | Required. Prompt id, or the exact text |
| `platform` | string | — | Optional. One platform; omit for all |
| `days` | integer | 28 | 1–365 |
| `limit` | integer | 10 | Readings per page, 1–50. A reading is one prompt on one platform on one day and carries every sample taken |
| `cursor` | string | — | `next_cursor` from the previous page |
Returns `answers`, `readings` (on this page), `has_more` and `next_cursor` (`null` at the end). Each answer carries `reading_id` and `sample` (its index within the reading), `prompt_id` and `prompt`, `platform`, `surface` (`consumer` or `api`), `country`, `day`, `answered_at`, `text` verbatim, `text_bytes`, `truncated`, `mentioned`, `cited_own_domain`, `citations` in order (`url`, `domain`, `position`, and `context`: the sentence of the answer that carries the link, `""` when the text does not mention it, `null` when it was never looked for; see [Citations and sources](/measure/citations-and-sources#what-was-each-link-cited-for)), `competitors_named`, `position` (your place among the names you track, absent when you were not named), `stance` (`positive`, `neutral`, `negative`, `none` or `unscored`; see [Sentiment](/measure/sentiment)), `brands` (the [full brand order](/measure/share-of-voice#the-full-brand-order), each brand with its own stance), `claims` (the [factual statements](/measure/claims) the answer makes about a brand, quoted), and `failed`. `brands` and `claims` are `null` until the answer has been read for them and `[]` when it was read and holds none. Answers over 8 KB are cut on a character boundary and flagged `truncated: true`, with `text_bytes` carrying the full length. A failed sample is listed with `failed: true` and an empty `text`, never dropped: a bad week must not look like a quiet one.
### answer\_slice
Read-only. No credits. REST: `GET /v1/memory/answers/slice`.
Every answer matching a predicate, verbatim. Terms AND together and select the sample; at least one is required.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `platforms` | string\[] | — | Any of the tracked platforms |
| `country` | string | — | ISO 3166-1 alpha-2 |
| `topic_id` | string | — | The prompts filed under this topic now. A prompt moved between topics answers under the topic it is in today |
| `prompt_ids` | string\[] | — | These tracked prompts, at most 100 |
| `surface` | enum | — | `consumer` is what a person sees; `api` the model's API |
| `model` | string | — | The start of the model's name, case-insensitive: `gpt-5` matches `gpt-5` and `gpt-5-mini` |
| `competitor_named` | string | — | A rival's domain the answer named or linked |
| `cited_domain` | string | — | A domain among the citations; subdomains match |
| `brand_named` | boolean | — | `true`: named the brand; `false`: did not |
| `sentiment` | enum | — | Your brand's stance in the answer: `positive`, `neutral` or `negative`; `none` was scored and took no stance; `unscored` was never scored |
| `brand_position_min`, `brand_position_max` | integer | — | Where your brand came in the answer's order of brands, 1 for first, inclusive. Either one implies the brand was named |
| `text_contains` | string | — | A phrase in the answer's text, matched literally and case-insensitively, at most 80 characters |
| `from`, `to` | string | — | First and last day, `YYYY-MM-DD`, inclusive |
| `days` | integer | 28 | Lookback when `from` and `to` are omitted, 1–365 |
| `limit`, `cursor` | | | Same paging as `answer_history` |
Same row shape and the same 8 KB cut as `answer_history`.
**Reading terms and answer terms.** `platforms`, `country`, `topic_id`, `prompt_ids` and the dates select readings. The rest are answer terms, and they must all hold for **one sample**: an answer that named the rival and a different answer from the same reading that cited the domain do not match together. When an answer term is set, only the samples that satisfy every term come back, and failed samples are excluded, because a sample that did not happen cannot be judged. With only reading terms, every sample of the selected readings is returned, failed ones flagged.
**`none` and `unscored` are different.** `none` is an answer we scored that took no stance on your brand. `unscored` is an answer that has not been scored at all. Neither is `neutral`.
**`text_contains` reads every answer in the window**, so without `topic_id` or `prompt_ids` the window is at most 30 days; a wider one is refused with what to narrow. For answers about a subject rather than a phrase, use the agent's [recall](/agent/recall). A slice too broad to read in one go is refused with what to narrow: a shorter window, `prompt_ids` or `topic_id`, a platform, or a shorter phrase.
### citation\_timeline
Read-only. No credits. REST: `GET /v1/memory/citations/timeline`.
One domain's or one page's citations per day, per platform, with mean position and how many prompts cited it — the same rollup `get_analytics` with `area: "source"` ranks, kept day by day.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `domain_or_url` | string | — | Required. A site (`g2.com`) or one page's URL |
| `days` | integer | 28 | 1–365 |
Returns `domain`, `url` (set when the question was one page), `total`, `prompts`, and `series` — one entry per platform, most cited first, each with `points` for every day the window touches: `day`, `citations`, `mean_position` (`null` when nothing that day carried a position) and `prompts`. A zero day is a real zero.
### page\_history
Read-only. No credits. REST: `GET /v1/memory/pages/history`.
One page's versions: every version captured, when, and what changed between them. It covers the workspace's own pages and the cited and competitor pages it watches. Use it before claiming a page changed.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `url` | string | — | Required. Absolute: one of your own pages, or a cited or competitor page the workspace watches |
Returns `url` (normalised), `known` (for your own page, whether it is in the workspace's inventory; for a watched page, always `true`), `versions` and `snapshots` oldest first — each with `id`, `first_seen_at`, `status`, `title`, `description`, `word_count`, `json_ld` (`present`, `absent` or `not_captured`), `canonical`, `robots`, `changed_bits` and `changes`, the fields that moved since the version before (`field`, `from`, `to`). Metadata on a version taken before it was recorded reads `not_captured`, and is never reported as a change. Any other page on another domain is refused with a `404`.
## Track
These tools change what gets measured. Reach for them when the answer is "we are not tracking the right things": "start watching these five buying-intent prompts", "stop wasting a slot on that one", "add the competitor who keeps showing up in answers".
### add\_prompts
Costs credits. Needs a read-and-write key. REST: `POST /v1/prompts`.
Adds one or more prompts to the workspace. From the next daily run, each new prompt is read across every platform your plan tracks. There is no `platform` argument — which surfaces a prompt runs on comes from the plan, not from the caller.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `prompts` | string\[] | — | Required. One or more prompt texts, none blank, at most 50 per call, each at most 500 characters |
| `topic` | string | — | Optional topic to file them under. Created if it does not exist |
| `country` | string | — | Optional ISO 3166-1 alpha-2 code, e.g. `US`. Defaults to the workspace's country |
Returns `added` — every prompt now being tracked, with `id` and `text`. A prompt that was already tracked comes back in `added` too, with its existing id, so a repeated call is idempotent all the way out; `skipped` is empty today. `credits_spent` and `credits_remaining` are `null`: tracking is charged as the prompts are read, not on this call. Your plan caps how many prompts you can hold at once, and a paste that would exceed it is refused with a `409` naming how many slots remain — see [Plans](/account/plans).
### remove\_prompt
Destructive. Needs a read-and-write key. REST: `DELETE /v1/prompts/{prompt}`.
Stops tracking a prompt and frees its slot. Confirm the prompt with `get_prompt` first — this cannot be undone.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. Prompt id, or the exact text |
Returns `id` and `removed: true`. No new readings are collected for that prompt afterwards, so any trend line for it ends on the day you remove it.
### add\_competitor
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/competitors`.
Tracks a competitor domain so it is scored alongside the project in competitor analytics. It does not create new readings, so it costs nothing.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `domain` | string | — | Required. Bare hostname, e.g. `example.com` — no scheme or path |
| `name` | string | — | Optional display name. Defaults to the domain |
Returns the competitor: `id`, `domain`, `name`. Competitor share of voice uses the same denominator as your own — successful runs naming the brand over all successful runs — so adding a competitor never changes your numbers.
### remove\_competitor
Destructive. Needs a read-and-write key. REST: `DELETE /v1/competitors/{id}`.
Stops scoring a rival. Past readings keep what they already recorded, so history does not move — the rival simply stops being scored from here on.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. Competitor id, from `get_analytics` with `area: "competitor"` |
Returns `id` and `tracked: false`.
## Inbox
Everything waiting on a human. Natural questions here sound like "is anything blocked on me?", "what should I deal with first?", "why has nothing happened since Tuesday?", "I connected GitHub — clear that off my list", "tell it the launch is on the 14th". See [Inbox](/agent/inbox) for what ends up there and what each act does.
Each row's `type` says which tool acts on it, and its `ask_kind` says what it asks of a person:
| Row | `ask_kind` | Act on it with |
| - | - | - |
| `work_item` | `approve` | `approve_work_item` (with an edit, for an article, post or rewrite) or `decline_work_item` |
| `work_item` | `do` | Nothing to approve: a copy handoff or an outreach draft the person sends themselves |
| `ask` | `approve` | `approve_ask` or `decline_ask` |
| `ask` | `answer` | Answered in its own session (`session_id`), not here |
| `task` | `answer` | `answer_inbox_question` |
| `task` | `do` | `resolve_inbox_task`, once the person has done it |
Approve, answer and decline only on the customer's instruction, never on your own reading of a row. Every act returns `next_item_ref` when something else is waiting: the row the ranking puts first now, readable with `get_inbox_item`.
### list\_inbox
Read-only. No credits. REST: `GET /v1/inbox`.
Everything waiting on a person: approvals the agent stopped for, questions, work it could not finish alone, failed automations, and approvals that expired unanswered.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `category` | string | `needs_you` | `needs_you` (waiting on a person) or `in_flight` (approved and running, and Done rows still being checked) |
| `view` | string | `list` | `list` returns every row, newest first, under `items`. `triage` returns the `needs_you` rows as decisions under `triage`; refused with `category: "in_flight"` |
Each row in `items` carries `type` (`work_item`, `ask` or `task`), `id`, `ref`, `ask_kind` (`approve`, `answer`, `do` or `info`), `title`, `reason`, `session_id` and `updated_at`, plus `ask_id` on an ask. A task row also carries what it asks: `task.question` (`prompt`, up to six `options`, and `fact_ref` when it fills a profile field), `task.steps` (numbered steps, each with an optional `value` to copy), and `task.verification` while a Done is being checked. `list_approvable` is `true` only on a proposed change that may be approved without opening it: one whose effect can be undone and that is classed safe.
`view: "triage"` is the Inbox's own ranking, the same one the dashboard, the morning brief and the daily email read:
```json Response theme={null}
{
"triage": {
"top": [
{
"key": "task:missing_integration:github",
"ask_kind": "do",
"label": "Connect GitHub · 7 waiting",
"count": 7,
"item_refs": ["t_66f1a2", "t_66f1b9", "…"],
"oldest_waiting_since": "2026-09-28T07:12:00Z",
"lead": { "type": "task", "ref": "t_66f1a2", "title": "Connect GitHub", "…": "…" }
}
],
"sections": [
{ "ask_kind": "answer", "label": "2 more questions", "count": 2, "decisions": ["…"] }
],
"total_open": 12,
"decisions": 5,
"merged_count": 9
}
}
```
`top` holds at most three decisions; everything after them is in `sections`, grouped by `ask_kind`. `total_open` counts rows and `decisions` counts decisions, the number the dashboard's badge shows. `merged_count` is how many sightings are not shown as rows of their own: repeats a row absorbed, and duplicates folded into another row.
Worth calling before concluding the agents are idle. A workspace with rows here is one where nothing is moving until somebody answers.
### get\_inbox\_item
Read-only. No credits. REST: `GET /v1/inbox/items/{ref}`.
One row by its ref, in any state, in full: the whole proposal a work item would make, the question a task asks and its answer once given, a done row as done.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `ref` | string | — | Required. A row's `ref` from `list_inbox`, or any `next_item_ref`. `t_`, `w_` or `a__` |
Returns the row with `type`, `id`, `ref`, `title` and the `task` or `work_item` behind it. To approve a specific version, copy `work_item.change.proposal.captured_at` from here into `approve_work_item`. A row the duplicate check folded into another carries `merged_into`, the ref of the row to read instead; one it only suspected carries `possible_duplicate_of` and is not hidden. A malformed ref is `400`; a ref from another workspace is `404`, the same as one that names nothing.
### answer\_inbox\_question
Needs a read-and-write key. Costs credits when the run that asked had already finished, because it starts a new turn on the answer. REST: `POST /v1/inbox/tasks/{id}/answer`.
Answers a task whose `ask_kind` is `answer` with the person's words, never your guess. The answer is saved to Company facts — the business profile field the question names, or a stated fact every later run reads — the row closes, and the run that asked is put back to work.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `task_id` | string | — | Required. The task's `id` |
| `answer` | string | — | Free text, at most 2,000 characters |
| `option` | string | — | One of `task.question.options`, instead of `answer` |
Send `answer` or `option`, not both. Returns the closed `task`, `fact` (where the answer went: `{kind: "profile_field" | "stated_fact", ref}`), `resume` and `next_item_ref`. `resume.status` says what happened to the run:
| `resume.status` | Means |
| - | - |
| `resumed` | The run has the answer: a running run reads it from its next step, and a finished, failed or suspended one starts a new turn on it |
| `queued_after_approval` | The run is waiting on an approval and reads the answer once that is answered |
| `not_started` | The run was cancelled, or no run asked. Nothing was started |
| `failed` | The answer is saved and the row closed, but the run could not be handed it. Retry from the row's page in the dashboard |
A second answer to the same row is `409`. A row that is not a question is `400`.
### resolve\_inbox\_task
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/inbox/tasks/{id}/resolve`.
Closes a task the agents raised.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. From `list_inbox`, and only for a row whose `type` is `task` |
| `state` | enum | — | Required. `done`, `dismissed` or `snoozed` |
| `snoozed_until` | string | — | RFC 3339. Required when `state` is `snoozed`, refused otherwise |
`done` means the thing the agent needed has actually been handled — the integration connected, the record added — not that you plan to handle it. Nothing is queued to run when you send it.
A task that carries a check (a DNS record, a connection, a line on a page) is not closed by `done`: it stays open with `verification.status: "pending"`, moves to `in_flight`, and closes on its own when the check passes. If the check still fails after its retries, the row comes back to `needs_you` with `verification.failure` saying why; sending `done` again checks again. Snoozing a row while its check is pending is `409`. Returns the task and `next_item_ref`.
### approve\_work\_item
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/inbox/{id}/approve`.
Says yes to a work item the agents stopped for, and lets them carry the change out — as proposed, or as the person edited it. Only a row whose `type` is `work_item` is approved this way.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. Work item id, from `list_inbox` |
| `expected_captured_at` | string | — | RFC 3339. The `work_item.change.proposal.captured_at` the person read. If the proposal was revised since, the approval is refused with `409` |
| `edited_value` | string | — | The person's full edit, at most 200 KB. Approves their version instead of the agent's |
Idempotent: approving twice approves once, so a retry after a timeout is safe. Approving is the customer's decision to make, not one to infer from the row. Returns the work item and `next_item_ref`.
`edited_value` is accepted only for an article, a LinkedIn post or a rewritten passage that is still waiting, and is refused with `400` for anything else (a fix is edited in its pull request). The edited change keeps the agent's original, and is always a claim: it waits for a person in every approval mode and never goes out on its own. An edit that adds or changes code is refused with `422`, and nothing is approved. An edit is checked against the workspace's rules like any draft; one that breaks a rule is `409`.
### decline\_work\_item
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/inbox/{id}/decline`.
Says no to a work item, with the reason why.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. Work item id, from `list_inbox` |
| `reason` | enum | — | Required. `wrong`, `not_now`, `already_done` or `never_touch` |
| `note` | string | — | The person's words, at most 1,000 characters. Required when `reason` is `wrong` |
The reason is required and is the point of declining rather than ignoring: it is what stops the same proposal coming back. `wrong` means the proposal is incorrect, `not_now` right idea wrong time, `already_done` handled elsewhere, `never_touch` do not propose this again for this page or topic. The reason and note are kept on the item and in its activity. Returns the work item and `next_item_ref`.
### approve\_ask
Needs a read-and-write key. The call itself spends nothing; the run it lets go carries on as before. REST: `POST /v1/inbox/asks/{id}/approve`.
Allows a paused run, once, the exact call it stopped to ask about: a row whose `type` is `ask` and whose `ask_kind` is `approve`.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `session_id` | string | — | Required. The row's `session_id`: an ask id is unique only within its session |
| `ask_id` | string | — | Required. The row's `ask_id` |
An ask is never edited: the yes is bound to the exact arguments the run asked about. Returns `session_id`, `outcome` (`allowed-once`) and `next_item_ref`. The run is back on the queue and has not made the call yet; poll it with `get_session`. A session in another workspace is `404`; an ask already answered, expired, or no longer the one the run is waiting on is `409`. An ask whose `ask_kind` is `answer` is answered in its session, not here.
### decline\_ask
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/inbox/asks/{id}/decline`.
Refuses the call a paused run asked for. The run reads the reason and note first, then carries on without the call.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `session_id` | string | — | Required. The row's `session_id` |
| `ask_id` | string | — | Required. The row's `ask_id` |
| `reason` | enum | — | Required. `wrong`, `not_now`, `already_done` or `never_touch` |
| `note` | string | — | The person's words, at most 1,000 characters. Required with `wrong` |
Returns `session_id`, `outcome` (`rejected`) and `next_item_ref`. Same `404` and `409` as `approve_ask`.
## Automations
Automations are scheduled work: a weekly digest, a recurring audit, a check that runs after every deploy. Natural questions here sound like "email me a summary every Monday", "what is already scheduled?", "run last week's report again now".
### list\_automations
Read-only. No credits. REST: `GET /v1/automations`.
Lists every automation in the workspace as `automations`: each with `id`, `name`, `instruction`, `trigger`, allowed `tools`, `status` (`active`, `paused`, or `done` for a `once` that ran), `timezone`, `run_at_minute`, `run_on_weekday` and `event_types` where set, and `last_run_at` (absent when it has never run). Takes no parameters.
Paused and retired automations are always listed. One the product paused on its own (for example a Daily check-in retired by [detection](/agent/findings)) carries `retired_reason` and `retired_at`. `next_run_at` is `null` for event automations.
### get\_automation
Read-only. No credits. REST: `GET /v1/automations/{id}`.
Full definition for one automation, with its most recent run inline as `latest_run` (`{id, status, state}`, or `null` when it has never run) — so "did my Monday report run?" costs one call, not two. A paused or retired automation is returned like any other, with `retired_reason` and `retired_at` when the product paused it.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
### create\_automation
Needs a read-and-write key. Creating costs nothing; each run it triggers does. REST: `POST /v1/automations`.
Defines a new automation that runs an instruction on a trigger. With no trigger it runs daily at 09:00 in the workspace's time zone.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `name` | string | — | Required |
| `instruction` | string | — | Required. What the automation should do each run, in plain language |
| `trigger` | enum | `daily` | One of `daily`, `weekly`, `monthly`, `quarterly`, `event`, `manual`, `once`. The clock triggers fire at `run_at_minute`, `monthly` and `quarterly` on the 1st; `event` fires on `event_types`; `manual` and `once` run only through `run_automation`, and `once` then retires |
| `tools` | string\[] | — | Optional allow-list of tool names the automation may use. Omit to allow everything the plan includes |
| `timezone` | string | The workspace's | Optional IANA zone the schedule is written in, e.g. `Europe/London`. UTC only if the workspace has none |
| `run_at_minute` | integer | 540 | Minutes past local midnight, 0–1439. `540` is 09:00. Only meaningful for the clock triggers |
| `run_on_weekday` | integer | — | For `weekly`: 0 = Sunday to 6 = Saturday |
| `event_types` | string\[] | — | Required and non-empty when `trigger` is `event`, refused otherwise. Any of `prompt_lost`, `citation_lost`, `new_deciding_source`, `competitor_changed`, `page_changed`. See [Event automations](/agent/autopilot#event-automations) |
Returns the created automation with its id. `next_run_at` is `null` for an event automation. Creation is free; every run spends credits like a manual run.
### run\_automation
Costs credits. Needs a read-and-write key. REST: `POST /v1/automations/{id}/runs`.
Triggers an automation immediately, outside its trigger. Returns a session handle — `session_id`, `status` and `poll_after_ms` — to poll with `get_session`, not the finished result.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
| `idempotency_key` | string | — | Strongly recommended. Sent as `Idempotency-Key`; a repeat call with the same key does not bill twice |
### delete\_automation
Destructive. Needs a read-and-write key. REST: `DELETE /v1/automations/{id}`.
Permanently deletes an automation and its schedule — this cannot be undone. Its past runs are sessions and survive. Confirm it with `get_automation` first.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
Returns `id` and `deleted: true`.
### update\_automation
Needs a read-and-write key, but does not spend credits. REST: `PATCH /v1/automations/{id}`.
Changes one automation in place — a partial edit. A field you do not pass is left alone, so pausing an automation cannot quietly rewrite what it does.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
| `name` | string | — | New display name |
| `instruction` | string | — | New instruction. Replaces the old one outright |
| `trigger` | enum | — | `daily`, `weekly`, `monthly`, `quarterly`, `event`, `manual` or `once` |
| `event_types` | string\[] | — | Only with an `event` trigger. Same values as `create_automation` |
| `run_on_weekday` | integer | — | For `weekly`: 0 = Sunday to 6 = Saturday |
| `tools` | string\[] | — | New allow-list. Replaces the old list |
| `status` | enum | — | `active` resumes it on its trigger; `paused` stops it firing without deleting it |
| `timezone` | string | — | IANA zone the schedule is written in |
| `run_at_minute` | integer | — | Minutes past local midnight, 0–1439 |
Pausing is how a schedule is stopped without losing it — deleting is the one that cannot be undone. Returns the automation in the shape shown under `list_automations`.
## Agent
The agent tools run a chat inside your workspace, with your tracking data in reach. Use them for questions that need reasoning across several slices — "why did our share of voice drop on Perplexity last week?", "draft a plan for the three prompts we lose most" — rather than for numbers you can pull directly with `get_analytics`.
### ask
Costs credits. Needs a read-and-write key. REST: `POST /v1/ask`.
Open-ended investigation the other tools cannot answer — why a prompt is losing, what to change on a page — run by an agent that crawls pages, audits them as a model sees them, and searches the web. Try `get_analytics`, `deciding_sources`, `win_plan`, `get_prompt` or `get_page` first: those are free and immediate, this costs credits and takes time.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `question` | string | — | Required. The question, in plain language, at most 4,000 characters — pass the question, not the data; the agent reads the workspace itself |
| `session` | string | — | Session id from a previous `ask`, to continue that thread instead of starting one |
Returns `session_id`, `status` (`running` at first — that is the success case, not an error), `answer` (`null` until complete) and `poll_after_ms`. Wait that long, then poll `get_session` with the returned id until `status` is `complete`.
The agent runs on an **agent model** — the LLM powering the chat. That is a different thing from a tracked **platform**, which is the answer surface Attensira reads on your behalf. Changing your agent model does not change what is measured.
### get\_session
Read-only. No credits. REST: `GET /v1/sessions/{id}`.
Fetches a session started by `ask` or `run_automation`: its status and, once finished, its answer. Use this to poll async work.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
| `include` | string\[] | — | Pass `["events"]` for the step-by-step trace of what the agent did. `events` is the only supported value |
Returns `id`, `status` (`complete`, `running`, `failed`), `state` (the engine's own finer word — a run parked on a question for you reads `awaiting_person` under a `running` status), `answer` (the agent's last message once complete; `null` while running or on failure), and `events` — present only when requested, each with a `type` such as `tool.start` or `text.delta` and the fields of that event — the way to see which tools the agent called and with what arguments.
## Sessions
A session is one piece of agent work: an `ask`, an automation run, a chat or the set-up run. These tools list them and act on them. REST twins are on [Sessions](/api/sessions).
### list\_sessions
Read-only. No credits. REST: `GET /v1/sessions`.
Sessions, newest first. Use it for "what is running right now?" or "what ran since Monday?".
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `status` | string\[] | — | `complete`, `running`, `failed`, or a stored state: `queued`, `running`, `awaiting_person`, `suspended`, `completed`, `failed`, `cancelled` |
| `origin` | string\[] | — | `user`, `automation`, `mcp`, `setup` |
| `since` | string | — | RFC 3339 or `YYYY-MM-DD` |
| `limit` | integer | 50 | 1–200 |
Returns `sessions`, each with `id`, `title`, `origin`, `status`, `state`, `automation_id`, `created_at` and `ended_at`.
### cancel\_session
Needs a read-and-write key. No credits; works on a spent balance. REST: `POST /v1/sessions/{id}/cancel`.
Stops a running session before its next step. Credits already spent stay spent. Safe to repeat: if the run already finished, it returns that real ending unchanged.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
### delete\_session
Destructive. Needs a read-and-write key. REST: `DELETE /v1/sessions/{id}`.
Permanently deletes a finished session and its trace. A running session is refused with a `409`; cancel it first.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
Returns `id` and `deleted: true`.
### add\_context
Needs a read-and-write key. Costs credits only with `run: true`. REST: `POST /v1/sessions/{id}/context`.
Gives a session text or links it cannot find itself, as a message in its thread.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required |
| `text` | string | — | At most 20,000 characters |
| `urls` | string\[] | — | `http` or `https`, at most 10 |
| `run` | boolean | `false` | `true` queues the session again to act on it now, which costs credits. Otherwise the next turn reads it, and a run already in flight picks it up when its current turn ends |
Pass `text`, `urls`, or both. A cancelled session refuses new context. Returns `session_id` and `status`.
## Account and settings
### get\_loop\_health
Read-only. No credits. Works on a spent balance. REST: `GET /v1/loop-health`.
Whether the workspace's loop is `working`, `idle` or `blocked`, with the reasons, and a day-by-day record of readings, automation runs and skips, findings, work produced, approvals pending, pull requests opened and re-reads due. **Call it first when asked why nothing happened.** See [Loop health](/agent/loop-health).
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `days` | integer | 7 | 1–30 |
Returns `status`, `reasons` and `days`. The per-day fields are documented under [`GET /v1/loop-health`](/api/endpoints#get-v1loop-health).
### get\_account
Read-only. No credits. REST: `GET /v1/account`.
Returns the org, every workspace you can reach, the one this call resolved to, the credit balance, which platforms that workspace tracks, and what this credential may do. Takes no required parameters. Call it first from a fresh integration — to collect the ids the `project` parameter takes, to confirm which workspace the credential points at, and before reporting any per-platform number: `platforms` is the authority on which platforms were queried at all, so a platform absent from it was never measured rather than measured at zero.
Returns `org` (`id`), `project` (`id`, `name`, `domain` — the workspace this call resolved to), `workspaces` (every workspace in the org, each with `id`, `name`, `domain`), `credits` (`available`, `available_usd`, `held_units`; absent when the balance could not be read, never a zero standing in for an outage), `scopes` — what this credential may do — and `platforms`. `work` counts this month's work by type: `committed` (a `null` count is a type not tracked yet, not zero), `delivered`, `waiting_on_you` (held for a person, which is not a shortfall) and the month's `cap_usd`. When it could not be read, `work` is `null` and `work_unavailable_reason` says why.
`project` is `null` when the organisation has several workspaces and the call named none. That is the answer rather than an error: `workspaces` carries the ids, and every other tool then needs an explicit `project`. `platforms` is empty in that case too, because platforms belong to a workspace rather than to the organisation. It answers without a workspace, so it is always the right call when another tool asks you to name one.
### get\_costs
Read-only. No credits. Works on a spent balance. REST: `GET /v1/costs`.
What a UTC month has cost, in US dollars: the total, a month-end forecast, where it went, and the work the budget committed us to against what was delivered. The same figures as the [Costs](/account/costs) page. Call it when asked what Attensira cost this month, or what a client's workspace spent.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `period` | string | this month | `YYYY-MM`, a UTC month that has started. Anything else is refused with the form to send |
| `project` | string | — | A workspace id, to narrow every breakdown to that workspace. **Omit for the whole organisation**, unlike most tools, where omitting it means your own workspace |
Returns `period`, `currency` (`usd`), `as_of`, `workspace_id` (the workspace named, or `null` for the organisation), and:
| Field | What it is |
| - | - |
| `spent_usd` | The month's spend, less refunds. Every breakdown below adds up to it. Ad spend is never part of it |
| `forecast_usd` | The month's spend at its pace so far: spend divided by days elapsed, times days in the month. `null` for the first three days, never a guess. For a past month, what it cost |
| `by_work_area` | Per project: `workspace_id`, `work_area_id`, `name`, `spent_usd`. A `null` `work_area_id` is the workspace's unassigned spend: chats and the Chief of staff |
| `by_workspace` | Per workspace: `workspace_id`, `name`, `spent_usd`. An empty `workspace_id`, named `Across workspaces`, is spend that belongs to the organisation rather than one workspace |
| `by_kind` | `kind` and `spent_usd`. `agent_work` and `data` carry today's spend; `paid_placement` and `creators` are always listed and read `0`; `unattributed` appears only when a charge could not be placed |
| `by_type` | Per deliverable `type` (`article`, `page_fix`, `listing_fix`, `outreach_pitch`, `monitoring_day`, `other`): `delivered` and `spent_usd`. `delivered` is `null` for a type we do not count, never `0` |
| `committed` | The organisation's commitment for the month, per type: `committed` (`null` for a type we cannot count), `delivered`, `waiting_on_you`, `shortfall` and `credit_usd`, the credit for undelivered work. For an open month, `credit_usd` is what the close would credit now. See [Plans](/account/plans#if-we-deliver-less-than-the-budget-committed) |
| `credited_back_usd` | What the month's close credited back to the balance for this month's shortfall |
| `ad_spend` | `usd`, `source`, `note`. Paid to the ad platform directly and never part of any total. `usd` and `source` are `null` with no ad account connected |
`committed` and `credited_back_usd` are the organisation's even when `project` names a workspace: one budget commits work for every workspace in it.
The budget, its pace, the caps, the balance and how it is funded are **not** returned. They are set by a person in **Settings → Billing**, and no tool can read or change them. Read the balance with `get_account`. When the work needs more room, tell the person: only they can raise a budget or a cap.
The REST twin is [`GET /v1/costs`](/api/endpoints#get-v1costs).
### get\_brief
Read-only. No credits. REST: `GET /v1/brief`.
The same brief the morning email carries: the artifact of the day, what needs a person, counts for the window, and what re-reads when. See [The morning brief](/agent/morning-brief).
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `date` | string | — | Optional `YYYY-MM-DD` for one of this workspace's own past days. Omit for today |
Returns `date`, `edition` (`daily`, or `weekly` for the Monday note), `subject`, `text` (the brief itself, as the email body), `order` (the weekly sections in the order the text reads them: shipped work and early signals before citations and goals), and `sections`, among them `needs_you` (at most three rows, each with an inbox `id` and `title`), `goals` (each goal's `name` and `pace`) and the citation wins and re-reads. A section that is absent is not wired yet, never a zero. It reports counts, never a day-over-day comparison, until a seven-day window exists, and says "no proven change yet" until a verified re-read.
### get\_ai\_traffic\_install
Read-only. No credits. **Key-only.** REST: `GET /v1/ai-traffic/install`.
Returns everything needed to start sending AI-crawler hits to Attensira: the ingest endpoint, the HMAC signing secret for your workspace, the signature header name, the replay window, the daily cap, and a copy-pasteable edge worker. Takes no parameters.
Call it when [AI traffic](/traffic/ai-traffic) is empty and you need to know how to send us any. The signing secret is derived from the workspace id, so it is stable and can be shown again — unlike your API key, which is displayed exactly once. Because the response carries a secret, a signed-in OAuth connection is refused here with a message saying so; use an API key.
### connect\_github
Needs a read-and-write key, but does not spend credits. REST: `GET /v1/github/install-url`.
Starts connecting a GitHub repository: returns the install link for the Attensira GitHub App, scoped to this workspace. Call it when a fix needs a pull request and the workspace has no repository connected.
Takes no parameters. Returns `url` — the App install link, and nothing else. GitHub hands back a user-to-server code only to a browser that completes the install, so the connection exists only once a person has followed the link. Each call mints a fresh single-use link, so fetch a new one rather than reusing an old one.
### update\_settings
Needs a read-and-write key, but does not spend credits. REST: `PATCH /v1/settings/agents`.
Sets how far a run may go on its own, and which paths act mode may touch. Absent fields are left alone. This is the customer's decision: change it on their instruction, never to finish a task. See [Approval](/agent/approval).
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `unattended_mode` | enum | — | `ask` stops for a person, `draft` leaves the work in review, `act` carries it through |
| `act_path_allowlist` | string\[] | — | Path patterns act mode may change, e.g. `/docs/**`. Replaces the list; empty restores the default. Source, CI, lockfiles and dotfiles are always refused |
| `publish_without_approval` | boolean | — | Whether a run may put a page on the site without asking |
| `send_without_approval` | boolean | — | Whether a run may send email without asking |
At least one field is required. Returns the settings in force: `unattended_mode`, the effective `act_path_allowlist`, `act_paths_are_default`, `publish_without_approval` and `send_without_approval`.
### list\_wins
Read-only. No credits. Works on a spent balance. REST: `GET /v1/wins`.
The workspace's wins, newest first. A win is the first time an engine named your brand, or linked to your site, in an answer to a prompt that does not name your brand itself, after we had read that prompt on that engine on at least three earlier days with a successful answer and none of them did the same. A failed read counts toward neither half. A prompt and engine pair can give one win of each kind, `named` and `linked`, and never the same kind twice. Only the first naming (or link) can be a win: one that came before those three days of reading is not, and no later answer of the same kind on that prompt and engine becomes one.
A win is a first **since we started reading** that prompt. It is not evidence that any work caused it. Say it the way `sentence` does, and keep "since we started reading" when you repeat it.
Takes no parameters besides `project`. Returns at most the 50 most recently found wins. Dismissed wins are left out.
Returns `wins` and `unseen`. Each win carries:
| Field | Notes |
| - | - |
| `id` | The win's id |
| `prompt_id`, `prompt` | The tracked prompt and its text |
| `platform`, `engine` | The platform id (`chatgpt`) and its name (`ChatGPT`) |
| `kind` | `named`: the answer named the brand. `linked`: it cited a page on your domain |
| `sentence` | The win in one sentence, the same sentence the win email uses |
| `quote` | A verbatim excerpt of the answer around the mention, at most 400 characters. `null` when no answer that day gave an excerpt that passed the honesty check. A quote is never edited |
| `quote_truncated` | `true` when the excerpt stops short of either end of the answer |
| `cited_url` | The first page on your domain the answer cited, or `null` when it cited none |
| `date` | The day of the answer, `YYYY-MM-DD`, in the workspace's time zone |
| `reading_since` | The first day we read this prompt on this engine, `YYYY-MM-DD`. `null` only when it was not recorded |
| `detected_at` | When the win was found, RFC 3339. The list is ordered by this |
| `seen` | Whether someone has opened the win in the dashboard |
| `share_url` | The public link to the win card while it is shared, otherwise `null` |
`unseen` counts every win in the workspace that nobody has opened in the dashboard and nobody has dismissed, including any beyond the 50 listed. Reading the list through this tool does not mark a win seen.
```json Response theme={null}
{
"wins": [
{
"id": "win_4e19",
"prompt_id": "prm_8f21",
"prompt": "best crm for small teams",
"engine": "ChatGPT",
"platform": "chatgpt",
"kind": "named",
"sentence": "ChatGPT named Acme for the first time since we started reading this prompt on 2 September 2026, in an answer to 'best crm for small teams' on 28 September 2026.",
"quote": "For a team under ten people, Acme and Rival are the two most often recommended, and Acme has the simpler setup.",
"quote_truncated": true,
"cited_url": null,
"date": "2026-09-28",
"reading_since": "2026-09-02",
"detected_at": "2026-09-28T10:05:00Z",
"seen": false,
"share_url": null
}
],
"unseen": 1
}
```
## Memory
Two writes into what the workspace knows about itself. See [Knowledge and skills](/agent/knowledge-and-skills) and [What we know](/agent/what-we-know).
### record\_observation
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/observations`.
Writes down something durable about this workspace so every future run starts knowing it — a pattern across answers, a source that keeps deciding.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `statement` | string | — | Required. One sentence carrying its own sample size — "7 of 9 answers that named us cited the changelog", not "the changelog does well" |
| `evidence` | object\[] | — | Required, at least one. Each with `kind` (`reading`, `citation_day` or `action`) and `id` exactly as another tool returned it — a `reading_id` from `answer_history`, for instance. Optional `url` and `day` labels are never what is checked |
Ids are resolved against the workspace before anything is stored, so a guessed one is refused rather than remembered. Returns the observation with its `id`, `statement` and `n`.
### write\_skill
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/skills`.
Records a rule this workspace wants followed — how to name things, a claim never to make, a procedure. Every future answer and draft obeys it. Write one the moment a customer states a preference as a rule ("always…", "never…"). Creates only; revising is done in the dashboard.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `title` | string | — | Required. The rule in a few words |
| `description` | string | — | Required. One line on when it applies, e.g. "On any page naming a competitor" |
| `body` | string | — | Required. The rule itself, in the customer's own words, specific enough to follow without asking |
| `kind` | enum | — | Required. `voice` is how it sounds, `compliance` what may never be claimed, `playbook` a procedure to follow |
Returns the skill with its `id`, `title` and `kind`.
## Set up a workspace
These tools let a person's assistant set up a business from its domain: the agents draft it, the person decides, and the person pays in their browser. Neither takes `project` — there is no workspace to select yet — and neither can charge anything. Sending `project` anyway is refused with the reason.
The order is: `start_workspace` → show the person what was drafted and ask → `confirm_setup` with what they kept → give them the checkout link → call `start_workspace` again until it reads `active`. The [first recipe](/mcp/recipes#set-up-a-business-from-your-assistant) walks through it.
### start\_workspace
Needs a read-and-write key or a signed-in connection. No charge. REST: `POST /v1/onboarding/workspace`.
Creates the workspace for a domain, or returns the one already started for it. Calling it again with the same domain never creates a second workspace: it is how you poll.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `domain` | string | — | Required. The business's domain, e.g. `example.com` |
Returns `workspace_id`, `domain`, `status`, `reason` (why it needs input, else `null`), `next` (what to do now, in one sentence), and the draft: `profile` (facts read off the site, each with the page it came from, or a `question` to ask when nothing was found), `competitor_suggestions`, `competitor_question`, `platform_suggestions`, `proposed_goals`, `plans` and the `budget` range.
`status` moves in this order:
| `status` | Means |
| - | - |
| `drafting` | The site is being read. Call again shortly |
| `needs_input` | The profile could not be drafted; the questions in `profile.facts` fill it |
| `ready_to_confirm` | Everything is proposed. The person decides |
| `awaiting_payment` | A plan is chosen; the checkout is the person's to finish |
| `active` | The plan is paid and the first readings are being taken |
| `first_readings_ready` | Enough of the first readings have landed to read a brief from |
A signed-in connection works before there is any workspace: the first call sets up your organisation. An account can hold one workspace waiting for payment at a time: starting a second domain before the first is paid is refused, naming the one to finish.
### confirm\_setup
Needs a read-and-write key or a signed-in connection. No charge. REST: `POST /v1/onboarding/workspace/{id}/confirm`.
Saves the person's setup choices and returns their checkout link. Every call mints a new link, so give the person the latest one.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `workspace_id` | string | — | Required. From `start_workspace` |
| `plan` | string | — | Required. A plan `id` from `start_workspace`'s `plans` |
| `monthly_budget_usd` | number | — | The monthly budget the person chose, in US dollars |
| `overage_cap_usd` | number | — | The most a month may be charged in all, in US dollars: from the budget up to 10% over it, and only sent with `monthly_budget_usd`. A cap above the budget lets new work run up to 10% over the budget, which stays the budget. A cap below the budget or more than 10% over it is a `400`. Omit it and work pauses at the budget, which is then the cap |
| `competitors` | string\[] | — | The rivals they kept, by domain |
| `platforms` | string\[] | — | The platforms they kept |
| `goals` | object\[] | — | The goals they kept, as `start_workspace` returned them in `proposed_goals` |
| `profile_edits` | object | — | Corrections to the profile, a fact's `field` mapped to its new value |
Returns `workspace_id`, `status`, `checkout_url`, `requires_browser` (always `true`), `expires_at` (`null` unless the payment provider states one), `plan`, `budget_consent` and `next`. `budget_consent.line` is the top-up rule the person agrees to by paying, in words: relay it as written. `budget_consent.monthly_budget_usd` is the budget that was agreed; it is `null` only for a rule agreed before budgets were kept apart from caps. A person who already has a card on file is sent to the billing page to confirm the plan in person, because an assistant has not been given that yes. An account that already pays for a plan has nothing to check out, and the call says so.
## Goals
What the agents aim at. A goal is a metric, a target and a date; the daily readings say whether the work is on pace.
### get\_goals
Read-only. No credits. REST: `GET /v1/goals`.
Every goal in the workspace, with its current value against its target and whether the work is on pace. Takes no parameters.
Returns `goals`, each with `id`, `name`, `metric` and `metric_label`, `unit`, `direction`, `baseline` and `current` (each `{value, n}`; a `null` value is not measured yet, not zero), `target`, `target_date`, `pace` (`state` is `ahead`, `on_pace`, `behind` or `unmeasured`, with the `expected` value and a `reason`), `sentence` (the standing in words), `work_area_id` and `work_area_ids` (the projects it drives), `status` and `created_via`. The pace verdict is Attensira's: quote it rather than comparing the numbers yourself.
### set\_goal
Needs a read-and-write key, but does not spend credits. REST: `POST /v1/goals` and `PATCH /v1/goals/{id}`.
Creates a goal, or changes one by `id`. On a change, fields you omit are left alone. Set goals on the person's instruction.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | The goal to change. Omit to create one |
| `name` | string | — | Required to create |
| `metric` | string | — | Required to create. A metric key as `get_goals` returns them, such as `ai_share_of_voice` or `search_clicks` |
| `target` | number | — | Required to create. In the metric's own unit |
| `target_date` | string | — | Required to create. `YYYY-MM-DD` |
| `work_area_id` | string | — | The project it drives |
| `idempotency_key` | string | — | Any string you choose. A retried create with the same key writes once |
Returns the goal's `id`. A change with nothing in it is refused rather than reported as a change.
## Projects
A project is an area of work with its own crew of agents. The crew plans and does the work; a person steers it. Remember that on the wire a project is a work area, and `project` is the workspace.
### list\_projects
Read-only. No credits. REST: `GET /v1/work-areas` and `GET /v1/work-areas/{id}`.
Without `id`, every project in the workspace. With `id`, one project and its latest feed.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | A project's id (its `work_area_id`) |
The list returns `projects`, each with `id`, `name`, `description`, `status`, `team` (each crew member's `role`, `name` and `status_line`), `goal_id` and `goal_text`, `unread`, `needs_you`, `live`, `autonomy`, `budget_cap_usd` (the project's monthly cap, `null` when none is set) and `last_activity_at`. One project adds its `direction` (what people have told it, word for word), `triggers`, `automation_ids` and `feed`: the latest posts, each with `kind`, `text`, `at`, `because` and its `author`.
### steer\_project
Needs a read-and-write key. Posting is free; the crew's reply is an agent run and spends credits. REST: `POST /v1/work-areas/{id}/steer`.
Gives a project's crew the person's direction, in their words. It is posted to the project's feed and kept as the project's direction, and the crew answers it with an ordinary run that replans. A steer is a post and nothing else: it never approves work, changes a setting or publishes anything, whatever the words say, and the run that answers it meets the same approvals as every run.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `id` | string | — | Required. The project's id |
| `text` | string | — | Required. The person's direction, verbatim, at most 4,000 characters |
| `idempotency_key` | string | — | A retried post with the same key posts once |
Returns `ack` (the sentence to tell the person), `feed_item_id`, `run_id` (the run answering it, `null` when it could not start, with `reply_error` saying why) and `plan_change`, which is always `null`: a steer changes no plan by itself, and each change the crew makes lands in the feed as its own post. `replayed` is `true` when the key named a post already sent, and nothing was posted again.
## Deliverables
### list\_deliverables
Read-only. No credits. REST: `GET /v1/deliverables`.
What the crews delivered, newest first: where each piece of work lives and what the re-reads proved about it.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `type` | enum | — | `article`, `page`, `fix`, `social_post`, `listing_fix`, `product_copy` or `image` |
| `work_area_id` | string | — | One project's deliverables |
| `status` | string | — | Delivery state to filter on. An unknown one is refused with the valid list |
| `cursor` | string | — | `next_cursor` from the previous page |
Returns `deliverables` and `next_cursor` (`null` at the end). Each deliverable carries `id`, `type`, `title`, `work_area_id`, `location` (`provider`, `label` and `url`: the pull request, CMS draft or post where it lives), `status` and `status_label`, `result`, `approved_by` (a name), `run_id` (the session `get_session` reads), `run_url` (the replay at the step that made it), `dashboard_url`, `created_at` and `shipped_at`. `result` is `null` when there is nothing to measure yet; otherwise it has a `state`, a `label` and a `verdict` that is `null` until a re-read reached one.
## Reports
### get\_report
Read-only. No credits. REST: `GET /v1/reports/latest`.
The latest published client report for a cadence.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `cadence` | enum | `week` | `week`, `month` or `quarter` |
Returns `id`, `title`, `cadence`, `period`, `facts` (each counted over its own `n`), `movement_proven`, `not_read` (what the report could not read), `markdown`, `published_at`, and `share_url` with `share_expires_at` when the report is shared. Through MCP the markdown is capped at 16 KiB and `truncated` says when it was cut: check it before quoting the end of a report, and send the person to the report page for the whole of it. Quote the facts as they are rather than recomputing them. See [Client reports](/agencies/client-reports).
## Markets and brand family
Which countries and languages the workspace is read in, and which domains and names count as the brand. Reading both is free. Changing either is the person's, in the dashboard: more markets means more AI responses a month, and a property changes what counts as "you" in every number.
### get\_markets
Read-only. No credits. MCP-only.
The workspace's markets, primary first, the platforms read in them, and how each platform is located in each market. Takes no parameters.
Returns `markets` and `support`. A platform that cannot be located in a market is `unsupported` there, and is not read in it: that is not measured, never zero. To compare markets, call `query_evidence` with `group_by: ["country"]`.
### preview\_markets
Read-only. No credits. MCP-only.
Prices a change to markets or platforms in AI responses a month, against what the plan includes. It changes nothing.
| Parameter | Type | Default | Notes |
| - | - | - | - |
| `markets` | object\[] | — | Required. The whole list after the change, primary first. Each `{country, language?}`: a two-letter country code and an optional BCP-47 language |
| `engines` | string\[] | — | The platforms after the change |
| `topic_ids` | string\[] | — | Scope the change to these topics, which is how a second market is usually added |
Returns `responses_per_month` for the organisation and `workspace` for this workspace alone, each now and after the change, the plan's monthly `allowance`, and `over_by`: how many responses a month the change lands over the allowance, `0` when it fits. `overage_policy` says what happens past the allowance.
### get\_brand\_family
Read-only. No credits. MCP-only.
The brand's own properties beyond its main domain: other domains, subdomains, products and sub-brands. Takes no parameters.
Returns `properties`, each with a `status` of `proposed`, `confirmed` or `rejected`. Only confirmed properties count as the brand in the numbers. For a number per property, call `query_evidence` with `group_by: ["property"]`.
# MCP troubleshooting
Source: https://docs.attensira.com/mcp/troubleshooting
Fix sign-ins that never return, 401s, scope errors, missing tools, revoked credentials, empty model results, and timeouts on the Attensira MCP server.
Most MCP failures are one of a handful of things: an OAuth sign-in that never came back, a malformed key, a client that cannot speak the transport, a revoked credential, a token missing the scope a tool needs, a model that was never queried, or a request that ran past the upstream budget. Each has a distinct signature.
## The browser opened but never came back
You started an OAuth connect, a tab opened, you signed in — and the client still says it is waiting. The sign-in worked; the redirect back to the client did not.
For a **local client** — Claude Code, `mcp-remote`, most editors — the callback goes to a loopback address such as `http://127.0.0.1:51234/callback`, on a port the client picked when it started. If that tab shows "connection refused" or hangs, the client is no longer listening on it. That happens when the client was restarted, quit, or timed out while you were signing in. Close the tab, restart the connect from the client, and complete the sign-in without leaving it sitting.
If the callback tab loads but the client never notices, something is intercepting loopback traffic. A corporate proxy or VPN configured to route *all* traffic, including `127.0.0.1`, breaks this — the browser sends the callback to the proxy and it never reaches your machine. Exempt `localhost`, `127.0.0.1` and `::1` from the proxy, or connect off the VPN once and let the client refresh from then on.
A proxy that intercepts TLS can also break the step before that. The client fetches discovery documents from `mcp.attensira.com` and `clerk.attensira.com`; if either is rewritten or blocked, the flow dies before a browser ever opens. Check both are reachable from the machine running the client:
```bash Shell theme={null}
curl -s https://mcp.attensira.com/.well-known/oauth-protected-resource/mcp
curl -s https://clerk.attensira.com/.well-known/oauth-authorization-server
```
Both should return JSON. If one returns HTML, a proxy is answering for it.
If the browser opens to a sign-in you cannot complete because the client runs on a server with no display, that client should be using a static API key instead. See [Connect a client](/mcp/connect).
## Why did it stop working after a while?
An OAuth access token is short-lived. A connection that works, keeps working for a while, and then 401s on every call has a client that is not refreshing.
Well-behaved clients refresh silently and you never see this. When you do, it is usually one of three things:
* **The client did not store the refresh token.** Reconnect; if it comes back an hour later, the client is at fault and the workaround is a static key.
* **The client kept an old refresh token.** Refresh tokens rotate — each use returns a new one and consumes the old. Replaying a consumed refresh token revokes the whole chain, which looks exactly like being logged out for no reason. Reconnect once; if it keeps happening, the client is holding a stale copy, often from a config file it restores on start.
* **The connection was withdrawn.** Withdrawing stops the next refresh, but the access token in flight keeps working until it expires — which is precisely the "worked for a while, then died" shape. See [Connected applications](/account/connections).
Time skew produces the same symptom early: a machine whose clock is more than a minute off can have a token that reads as expired the moment it is issued. Check the clock before blaming the client.
## Why is it reading the wrong workspace?
Approving a client fixes the organisation, never a workspace, and a static key carries the workspace it was minted in. Either way it is a default rather than a limit: if `get_account` names a workspace you did not intend, you do not need to reconnect — tell the client to name the right one:
Call `get_account`. Its `workspaces` array carries every workspace id in the organisation, and `project.id` is the one being used by default.
Every tool that works on a workspace takes an optional `project`. Ask for the workspace by name and the client will pass the id — "check visibility for the acme.com workspace".
Working in two workspaces at once needs one connection, not two server entries. What the approval fixes is the organisation, and only reaching a different organisation needs a fresh sign-in.
## A tool says the organisation has several workspaces
The message reads roughly *"this organisation has several workspaces: pass project=\ to say which one."* A signed-in connection names your organisation and nothing narrower, so when the organisation owns more than one workspace there is no default for a tool to fall back on.
Call `get_account`. It answers without a workspace — that is why the message points at it — and its `workspaces` array carries the ids. Then name one: "check visibility for the acme.com workspace", and the client passes the id as `project`.
You will see `project: null` in that reply, which is correct and not a fault. It says no workspace resolved for *this* call, and `platforms` is empty for the same reason — platforms belong to a workspace, not to the organisation. Name a workspace and both fill in.
A static key does not hit this: a key is minted inside one workspace and carries it as its default. See [API keys](/account/api-keys).
## Why does one tool fail with 403 when the rest work?
Reads succeed and a write tool — `add_prompts`, `create_automation`, `run_automation`, `remove_prompt`, `delete_automation`, `resolve_inbox_task`, `add_competitor`, `ask` — fails. The credential is a **static key minted as read-only**, and it simply does not carry write access:
```json 403 response theme={null}
{"error":{"type":"forbidden","message":"this API key does not have the write scope","status":403}}
```
```http Response header theme={null}
WWW-Authenticate: Bearer error="insufficient_scope", scope="write"
```
It is a `403` and not a `401` deliberately — the credential is fine, so a client that throws it away and reconnects is doing the wrong thing. Keys carry a scope chosen at mint time and cannot be widened afterwards: mint a read-and-write key and swap it in. `get_account` returns the scopes in force, which is the fastest way to settle the question.
An OAuth connection never sees this. Approving a client grants read and write — there is no read-only consent — so a `403` on a signed-in connection is not a scope problem. The one tool that refuses a signed-in connection on purpose is `get_ai_traffic_install`: its response carries your workspace's signing secret, and a consented token must not be able to read one credential out and mint another. The refusal says so, and the fix is to call it with an API key.
## Why am I getting a 401?
Your credential is missing, malformed, expired, or not for this server. The MCP endpoint answers:
```json 401 response theme={null}
{"error":"invalid_token","error_description":"The access token is invalid or expired"}
```
```http Response header theme={null}
WWW-Authenticate: Bearer realm="attensira", error="invalid_token", error_description="The access token is invalid or expired", resource_metadata="https://mcp.attensira.com/.well-known/oauth-protected-resource/mcp"
```
That message is identical for every cause — missing header, wrong shape, expired token, wrong audience, bad signature. It does not narrow anything down on purpose, because a `401` that explained itself would help someone probing with stolen tokens more than it helps you.
**On OAuth**, read the two sections above first: an hour-long lifetime and a revoked connection cover most of it. A token minted for a different service will also fail here — Attensira access tokens are bound to `https://mcp.attensira.com/mcp` specifically and are not accepted anywhere else, nor is anything else accepted here.
**On a static key**, check the shape. A valid key matches `^atn_live_[0-9a-fA-F]{32}$` — the literal prefix `atn_live_` followed by exactly 32 hexadecimal characters. Count them. Copying from a terminal that soft-wraps, or from a config file where an editor trimmed the line, is the usual cause of a key that is one or two characters short. The server checks the format before it makes any upstream call, so a truncated paste fails instantly and the request never reaches your workspace.
Then check the surrounding shape. The header value must be `Bearer ` plus the key, with a single space and no quotes around the key itself. If your config uses an environment variable, confirm the variable is actually set in the environment the client runs in — a GUI app launched from the dock does not inherit a variable exported in your shell profile. Print it with `echo ${ATTENSIRA_API_KEY:0:9}` to confirm the prefix without exposing the rest.
To separate a network problem from an auth problem, hit the unauthenticated health path: `curl -i https://mcp.attensira.com/healthz`. If that answers and your authenticated call does not, the credential is the problem.
## Why can't my client see the tools?
The server connected but no tools appeared, or the client reports the server as failed with no detail. This is almost always transport: Attensira speaks **Streamable HTTP only**, and a client that speaks stdio alone cannot reach an HTTPS endpoint directly.
Clients that connect natively include Claude Code, Claude Desktop, Claude.ai, Cursor, VS Code + Copilot, Windsurf, Cline, Codex CLI, ChatGPT, Zed and n8n. In Zed, add Attensira as a remote server (a `url` entry), not a local command. Older builds of several clients launch local processes only, and need the `mcp-remote` bridge:
```bash Shell theme={null}
npx -y mcp-remote https://mcp.attensira.com/mcp \
--header "Authorization: Bearer ${ATTENSIRA_API_KEY}"
```
The bridge requires Node.js, and its first run downloads the package, so give it a few seconds before deciding it failed. Full per-client configuration is in [Connect a client](/mcp/connect).
If your client does connect natively and still shows nothing, check two more things. Some clients only load servers added before the conversation started — open a new chat. And some cache the tool list, so a restart or an explicit refresh in the MCP panel is needed after editing config. A healthy connection lists the tools in the [tool reference](/mcp/tools), `get_account` among them.
## Why did my key stop working?
A key that worked yesterday and 401s today has been revoked, either by you or by a workspace admin under Settings → Your assistant at [app.attensira.com/settings/access](https://app.attensira.com/settings/access). Revocation takes effect on the very next request, for every client using that key. Withdrawing an OAuth connection is not as immediate: the access token already in flight survives until it expires, because the MCP server verifies tokens locally and never asks us whether one is still wanted.
Keys cannot be recovered. Only a hash is stored, so there is no screen anywhere that will show you the original value again — not for you, and not for support. The fix is always to mint a new key and update every client that used the old one.
When you rotate, update every place the key lives: shell profiles, `claude_desktop_config.json`, `~/.cursor/mcp.json`, `~/.codex/config.toml`, VS Code secret storage, n8n credentials, and any connector configured with a header. A client left on the old key fails silently in the background. Clients connected over OAuth are unaffected by a key rotation — they hold a different credential entirely.
If nobody revoked anything, re-read the 401 section — a config edit that dropped a character produces the identical error.
## Why does a model show no data?
Three different states look like "nothing" and mean very different things. Read the fields before concluding the model failed you.
* **`tracked: false`** — this model is not configured for your workspace, so it was never queried. Reporting it as 0% would invent a failure that did not happen. A new workspace tracks four models by default, `chatgpt`, `perplexity`, `claude` and `google_ai`; Gemini (Growth and above) is switched on in the dashboard.
* **`readable: false`** — no reader exists for that surface over this route, so Attensira collects nothing from it. `google_ai` has no API reader, for example; it is still read through the consumer surface, so Google AI Overview answers do arrive.
* **`value: null`** — not measured in this window. Null is not zero.
* **`value: 0` with `n > 0`** — a real, measured zero. The model was asked `n` times and never named you. That is a finding.
Deltas work the same way. A delta is `{value, real}` and must pass a two-proportion z-test at 95% before it is reported. Below that noise floor you get `{real: false, value: null}`, which means "no change we can prove" — never "no change".
Sampling depth also explains thin results: each prompt × model × country runs at most once per workspace-local calendar day, with n=1 on Starter and n=3 on Growth, Business and Agency. A prompt added an hour ago may have no readings until the next day's run. Failed runs are stored but excluded from `n`.
For the full reading guide, see [Reading the numbers](/measure/reading-the-numbers).
## Why is my request timing out?
The upstream budget is 35 seconds. A tool call that needs longer than that upstream is cut off, and your client reports a timeout or an aborted stream rather than a partial answer.
The usual cause is an over-broad query: a wide date range across every prompt, page and source at once. Narrow it — one `area` at a time, a shorter window, a filter on a single topic or model — and the same question usually answers in a fraction of the budget.
Long-running agent work has its own path. `ask` may return `status: "running"` instead of a result; that is not a failure. Poll `get_session` for the outcome, passing `include: ["events"]` when you want the intermediate steps. Do not re-issue `ask` while a session is running — it spends credits again.
Other server limits worth knowing when you are debugging a stalled connection: request header timeout 10 seconds, full request read 60 seconds, idle connection 120 seconds, and a maximum response size of 8 MiB. A response that would exceed 8 MiB is a signal to page or filter the query rather than retry it.
Still stuck? Start from [Connect a client](/mcp/connect) and reproduce with the exact block for your client, then check `https://mcp.attensira.com/healthz` to rule out reachability. If you are writing the client yourself, the wire detail is in the [OAuth reference](/mcp/oauth).
# The analytics overview
Source: https://docs.attensira.com/measure/analytics-overview
Analytics opens on four questions: are we getting better, where are we losing and to whom, what did we do and did it work, and what changed out there. Every answer carries its n.
**Analytics → Overview** is where the analytics open. It answers four questions, in the order they are usually asked:
1. [Are we getting better?](#are-we-getting-better)
2. [Where are we losing, and to whom?](#where-are-we-losing-and-to-whom)
3. [What did we do, and did it work?](#what-did-we-do-and-did-it-work)
4. [What changed out there?](#what-changed-out-there)
Each question is one block. Every block has **Ask about this**, which opens a chat with the agent on that block's own numbers, and **See all**, which goes to the tab the block summarises (on question 4, each of its three lists has its own **See all**).
The window control above the blocks sets 7, 30 or 90 days, and the window is part of the URL, so a reload or a pasted link lands on the same window. Arriving from a row of the **Markets** table narrows the overview to that market, and the header offers the way back to every market.
## Are we getting better?
One line: how often assistants name you (your [share of voice](/measure/share-of-voice)) per day across the window, with the number of answers read and one sentence under it.
### The sentence
The sentence is written in one place, on the server, so its wording cannot drift between surfaces. It always states **n**, and it says "a real change" only when the movement is one we can prove:
| What you see | What it means |
| - | - |
| *Named in 31% of answers, up from 26% (n=1,840), a real change.* | The change against the previous window of the same length cleared the [significance test](/measure/reading-the-numbers#why-does-a-change-say-real), and the window was read on at least 70% of its days. |
| *Named in 31% of answers (n=1,840), nothing we can prove yet.* | Any difference from the previous window is inside the noise of the sample. |
| *Named in 31% of answers (n=212, read on 9 of 30 days), nothing we can prove yet.* | The difference passed the test, but fewer than 70% of the window's days were read (or, when it says *coverage unknown*, how many days were read is not known). Two thin samples weeks apart are not a trend. |
| *No answers were read in this window yet (n=0), so there is nothing we can prove yet.* | Nothing was read. This is not a zero. |
If the markets read changed between the two windows, the sentence compares only the markets read in both, by country code, and names the rest: *Over the markets read in both windows (US, GB): named in 31% of answers (n=1,640), nothing we can prove yet. DE added, so left out of the change.* A market read before and not now is named as *no longer read*. When no market was read in both windows, the sentence says there is nothing to compare it with yet. Under the chart, a line says the same in full: *Germany added in this window. The change is measured over the markets read in both windows, so a new market does not show up as a move.*
The sentence never says why the number moved. Whether a change of ours moved it is question 3, and even there the answer is movement observed after the change, not proof of cause.
### Markers
A marker sits on each day one of our changes went live. Several changes on one day are one marker that names them. Delivered work that is not on a live page yet, such as a draft nobody has published, gets no marker, because no engine could have read it.
Hovering a day shows its rate and the n behind it.
### Goals
Under the line, each active goal is a chip with its current value, its target and whether it is on pace. A goal whose value was not measured shows a dash, not a zero. The chip opens the goal on the **Goals** tab.
## Where are we losing, and to whom?
The prompts in state **lost**: across the engines read in the window, you are named on none and a rival is named on at least one. See [Prompts](/tracking/prompts) for how a prompt's state is set.
The block shows the five worst, and says how many there are in total. Worst means:
1. lost on the most engines;
2. then the most searched (a prompt with no known search volume ranks after every prompt with one);
3. then the order of the prompts board.
Each loss shows:
* **The prompt**, which opens its drawer, with how many engines it is lost on, its topic, and how often you were named, with n.
* **Who is named instead**, on the engine where the loss is worst.
* **What decided it**: the [deciding source](/measure/deciding-sources) that carried the most weight on that engine, or across every engine if nothing was cited on that one. Your own domain is never named here. If no answer cited anything, the row says so.
* **What we are doing about it**: the latest work on that prompt and where it stands (Proposed, Approved, In progress, Declined, Delivered, or Paused at the spending cap), linked to the work item. With no work on the prompt, the row offers **Start**. Every row also links to the prompt's [win plan](/measure/win-this-answer).
**Start** does not create anything. It opens a chat with the loss written in: the prompt, how many engines it is lost on, the rival on the worst engine and how often you were named there, the deciding source and its citations, and a request to read the win plan, propose the first piece of work, and ask you before starting anything that spends. Nothing is sent until you press send.
If the work log for a prompt could not be read, the row says so and does not offer **Start**. Offering to start work that may already exist is how the same work gets paid for twice.
## What did we do, and did it work?
The last five changes that shipped in the past 90 days, newest first by the day they went live. Each change shows its kind, the day it went live (or was delivered, for work that is not on the web), the named rate before and after with n on each side, a verdict, and the reason for the verdict in one line.
The 90 days do not follow the window control. A change's last re-read lands about 34 days after it goes live, so a shorter look-back could only ever say **Too early**.
### The verdict words
The verdict comes from the change's [re-reads](/measure/before-and-after), read across every tracked prompt and engine the change was meant to move. These are the four words, with the text you see when you hover each one:
| Verdict | On hover |
| - | - |
| **Moved up** | After this went live, the prompts it was meant to move were named more often, by more than the noise in the sample. That is movement after the change, not proof the change caused it. |
| **Moved down** | After this went live, the prompts it was meant to move were named less often, by more than the noise in the sample. That is movement after the change, not proof the change caused it. |
| **Nothing we can prove yet** | We re-read the prompts it was meant to move. Any difference was within the noise in the sample, or there was too little measurement before it went live to compare against. |
| **Too early** | We re-read the prompts a change was meant to move 7, 14 and 30 days after it goes live. None of those re-reads has a verdict yet. |
How a change gets its word:
* **The latest re-read with a verdict decides.** The +30-day re-read replaces the +14, which replaces the +7, because a later window has more evidence. They are not averaged.
* **Moved up** when more prompt-engine pairs were named more often than less often, past the significance test. **Moved down** is the reverse. When some pairs went the other way, the reason line says how many.
* **Nothing we can prove yet** when no pair moved past the test, when pairs moved up and down in equal numbers, when there was too little measurement before the change to compare against, when the prompts it was meant to move are no longer tracked, when the change was never found on the live page, when its re-reads were stopped, when no tracked prompt is tied to it, or when no re-read is on record for it.
* **Too early** while no re-read has a verdict and one is still booked or being read (the reason gives the date of the next booked one), and for delivered work that is not live yet, whose re-reads are booked once it is.
The same four words, with the same rules, appear on a prompt's changes timeline in its drawer. There the verdict is about that prompt's engines only.
### Committed vs delivered
Under the changes, a **Committed vs delivered** line compares the work committed for the current period with the work delivered, counted in work units (articles, fixes and pitches), never in money. Committed and delivered work is not reported yet, so the line reads *Not available yet. Committed and delivered work is not reported yet.* in place of the counts.
## What changed out there?
Three lists of things that happened outside your site in the window: **Competitor moves**, **New deciding sources** and **Wrong claims about you**.
None of the three is reported yet. Each list shows its heading and *Not available yet. We are not watching for this yet.* instead of an empty list, because an empty list would say we looked and found nothing.
## When a block cannot be read
Every block is read on its own, with its own time limit, so one slow or failed read never takes the others down. A block is in one of three states:
* **Read**: the numbers, or a sentence saying the window had none ("No prompt is lost to a rival in this window").
* **Not available**: the data behind the block is not reported for this workspace yet. The block says *Not available yet.* and what is missing.
* **Failed**: the read went wrong or took too long. The block says *We couldn't load …* with the reason and a **Retry**. Nothing is lost by retrying; it is a read.
On every block, a dash and a zero mean different things:
> A dash means not measured: no answer was read for it. A 0 means we read the answers and the count was zero.
## Export
**Export CSV** in the title row writes the whole overview to one file. Every rate travels with its n, an unmeasured value is an empty cell rather than 0, and a block that was not read is written as a comment line with its state and reason, so a file opened months later cannot be mistaken for "nothing happened".
## More detail
Below the four questions, a closed **More detail** section keeps the engine split, the competitors and the markets tables.
## Related
* [Reading the numbers](/measure/reading-the-numbers): n, the dash, and when a change is real.
* [Before and after](/measure/before-and-after): the re-reads behind every verdict.
* [Who decides an answer](/measure/deciding-sources): the sources behind each loss, and the next move for each.
# Before and after: re-reads
Source: https://docs.attensira.com/measure/before-and-after
How Attensira checks whether a shipped change moved anything: three re-reads at +7, +14 and +30 days, each comparing a week before the change with a week around the re-read date, only once the change is live.
A change shipping is not the same as a change working. After something ships, Attensira reads the same tracked prompts again and compares them with how they read before. That comparison is the only place the product uses movement words.
## What starts the re-reads
Re-reads start when a change reaches the world:
* a pull request Attensira opened is **merged**;
* a rewrite, an article or a copy item is **marked shipped** with its live URL;
* an article is **published through a connector** such as [Notion](/agent/notion);
* an [off-site placement](/agent/off-site#how-a-placement-is-verified) is **verified**;
* a read of one of your own pages **sees a change** on it, whether we shipped it, you made it from our advice, or nobody suggested it. See [Page changes](/measure/page-changes).
The re-reads cover the tracked prompts attached to the change (for a pull request, the `prompt_ids` given to `propose_fix`). A shipped change with no tracked prompts attached is not re-read. A change seen on your page is measured on the prompts its suggestion names, else the prompts that cited the page; [Page changes](/measure/page-changes#the-before-and-after) has the order.
## Three re-reads: +7, +14 and +30 days
Each change is re-read three times, at **+7**, **+14** and **+30** days after it shipped.
Each re-read compares two windows of the prompt's daily reading slots:
* **Before:** the 7 slot days before the change. For a change seen on your page, the 7 slot days before the last read that still saw the old version, so the days the change may or may not have been live count on neither side.
* **After:** 7 slot days centred on the +N date.
A re-read is read, and its verdict sent, only once its after-window has fully happened: about 4 days after the +N date, so around **day 11, day 18 and day 34**. Both windows always end on or before the day it is read.
Every verdict states its sample size (`n`), and none is a day-over-day comparison.
## Is the change live?
Before measuring, the re-read checks that the change is on the live page:
* For a **rewrite** or **copy**, the exact new text must appear on the page.
* For a **new article**, the page must load with content.
If the change is not live, that re-read is recorded as **Not live yet**, with the reason in terms of what happened ("merged, but the new text is not on the page yet"), and nothing is measured.
A change seen on your page is not checked again: the read that saw it is the evidence it is live. If the page goes back before the re-reads run, they are cancelled.
## The verdicts
| Verdict | Meaning |
| - | - |
| **Moved** | Named more often after the change, on X of N prompt and engine pairs, across M answers, past the significance test |
| **Fell** | Named less often, past the same test |
| **No change** | The before and after windows do not differ past the test |
| **Too close to call** | A difference inside the noise |
| **Too few answers** | Not enough readings from before the change to compare against |
| **Not live** | The change was not found on the live page, so nothing was measured |
| **Re-read booked** | Not read yet; the date shown is the day it will be read |
Every result is a single dated reading. A **Moved** verdict is what lets the [morning brief](/agent/morning-brief), the [receipt](/account/receipt) and the [win plan](/measure/win-this-answer) use movement wording at all.
## The labels beside a page
Where a shipped change is shown against the page it changed, as in the **Last change** column of the [Pages](/traffic/pages) grid, its re-read result is shown as one of three labels, all read from one map:
| Stored re-read result | Label |
| - | - |
| `moved` | **moved** |
| `no_change` or `inside_noise` | **nothing we can prove yet** |
| `insufficient_baseline` | **too early** |
| No result yet: the re-read has not run | **too early** |
**moved** says the number moved after the change, past the significance test. It is observed movement, not proof that the change caused it: the re-read cannot rule out everything else that changed in the same weeks. "The windows agree" and "they differ by less than the noise" are different findings that earn the same honest sentence, so both read **nothing we can prove yet**. A result the map does not know also reads **nothing we can prove yet**, never **moved**.
Only the AI re-read carries a label. Google clicks and positions and AI-crawler fetches shown around a change are counts with their own sample sizes; no verdict is drawn from them. See [Google search](/measure/google-search).
## How a verdict reads
Each verdict, in its email and in the action log, opens with the windows it compared:
> Re-read at +14 days, read on 2026-10-06: 2026-09-15 to 2026-09-21 before the change, against 2026-09-26 to 2026-10-02 after it.
## Where you see them
* **Inbox → Done**: each shipped item shows its latest re-read result, or **Re-read booked** with its date. See [Shipped](/agent/inbox#shipped).
* **The prompt page**: **Before and after** lists this prompt's re-reads with their samples, windows and outcome. See [Prompts](/tracking/prompts#the-prompt-page).
* **The morning brief**: **What we read again, and when**, at most five prompts, soonest first.
* **The win plan**: `rereads_at` is the day the next booked re-read for that prompt is actually read.
* **Client reports**: the re-read verdicts are the only place a [client report](/agencies/client-reports) uses movement language.
# Boards
Source: https://docs.attensira.com/measure/boards
Charts kept as the questions that drew them: our boards and yours, what a pin stores, the CSV export, and a link that opens a board without signing in.
A board is a page of charts you come back to. It lives under **Analytics → Boards**, and it holds two kinds: **ours**, which we keep for every workspace, and **yours**, which hold what you pinned.
## A pin is a saved question, not a picture
When you pin a chart, the board stores the question that drew it: the metric, how it is grouped, the window and any topic or prompt it is filtered to, plus whether to draw it as a chart, a table or a single number. It never stores the numbers or an image of them.
Every time the board opens, each pin's question is asked again. A share of voice pinned on Monday shows Friday's share of voice when you open the board on Friday, and it is the same figure the rest of the dashboard shows that day.
The window behaves the way it was asked:
* A pin asked over **the last N days** keeps reading the last N days, ending today.
* A pin asked over **fixed dates** keeps reading those dates.
## Ours and yours
**Ours** are defined by us, the same for every workspace, and never stored, so a better default reaches every workspace the day it ships. They are read-only: you can open and export them, and nothing else. To change one, pin the questions you care about to a board of your own.
| Board | What it shows |
| - | - |
| **Overview** | Share of voice, and share of voice week by week over 90 days. Citation rate. Average position when named. Prompts by state. |
| **Engines** | Share of voice, citation rate and average position, each split by engine, and share of voice by engine week by week over 90 days. |
| **Search** | Clicks from Google and impressions in Google, week by week over 90 days. Average Google position. Pages by clicks from Google (top 20). Tracked keywords' rank. AI Overview presence by keyword. |
| **Traffic** | [AI crawler](/traffic/ai-traffic) fetches, and visits referred by AI answers, in total and by page (top 20 each). |
| **Ads** | Every Google Ads campaign with where it stands with you, and spend only for campaigns you approved. |
Unless a row says otherwise, a pin on our boards reads the last 30 days.
A board whose source this workspace has not connected still opens. The pins that read that source say why they have nothing to show, rather than drawing an empty chart that looks like a zero.
**Yours** are what you pinned. A workspace keeps up to **50** boards of its own, and each board holds up to **24** pins. Everyone in the workspace sees the same boards.
### The Ads board
The Ads board lists every campaign with its approval state: drafted, waiting for your approval, approved, failed before anything was built, or found in your Google Ads account but not managed by us.
**Spend is shown only for campaigns you approved.** A draft, or a campaign waiting for your yes, never carries a spend figure, whatever the Google Ads account reports, so nothing on the board reads as running before you said it could. Spend is month to date, in your account's currency. Each campaign without a figure says why: not approved yet, waiting for your approval, failed before anything was built, not managed by us, or not yet reported by Google this month.
In the dashboard the Ads board does not draw its campaign list yet. **Export CSV** on the Ads board writes one line per campaign: its name in `group_1_value`, its approval state in `group_2_value`, and, for approved campaigns, its spend in `value`. That spend is in micros (millionths) of the currency in `unit`, as the `metric` column `ad_spend_micros` says, and a campaign without a figure carries its reason in `error`.
## Pinning a chart
**Pin to board** sits under:
* the **Are we getting better?** chart on the Analytics **Overview**, and
* any chart, table or metric the agent draws in a [chat](/agent/what-the-agent-shows) from your measurements.
Press it, then pick one of your boards or name a new one; a new board is made and the pin goes on it. Only your own boards are offered, because ours take no pins. A board that already holds 24 pins cannot take another.
A view that was not drawn from a measurement, such as a draft or a card, has no question to keep, so it has no Pin control.
When a pin names a topic or a prompt, the question is checked as you pin it. A topic or prompt that is not in this workspace is refused then, rather than shown broken every time the board opens.
On your own boards you can also:
* **Rename** the board from its title.
* **Remove** one pin.
* **Delete** the board. Its pins go with it; they are only questions, and the data they read is untouched. Deleting a board also turns its link off.
Making a board, pinning, renaming and reordering are refused when the org's credits are used up. Removing a pin, deleting a board, exporting, sharing and turning a link off always work.
## Reading a pin
Every pin carries its **n**: how many observations its rows stand on, summed across the rows. Read it first, for the reasons in [Reading the numbers](/measure/reading-the-numbers).
* A cell nobody measured is drawn as an **em dash**, never as 0. A **0** means we read the answers and the count was zero.
* A pin with no rows in its window says "Nothing in this window yet."
* A pin that hit its row limit says how many rows it is showing.
* Coverage gaps are named under the pin.
**One pin failing never fails the board.** A pin whose question could not be answered says so in its own place, and every other pin still shows its answer. A topic or prompt deleted after it was pinned is one way this happens.
## Exporting a board
**Export CSV** downloads the board as one table, with one line per row of every pin. Both our boards and yours export.
| Column | What it holds |
| - | - |
| `board`, `pin` | The board's name and the pin's title. |
| `metric` | The metric the pin asks for. |
| `window_from`, `window_to` | The days the answer covers, both inclusive. |
| `group_1`, `group_1_value`, `group_2`, `group_2_value` | What the row is grouped by, and its value: an engine, a week, a page. Empty for a single number. |
| `value`, `unit` | The answer and what it is measured in. |
| `n` | What that row stands on. |
| `error` | Why the pin has no answer, when it has none. |
Two rules carry over from the dashboard:
* **An unmeasured value is an empty cell, never 0**, and its `n` is still written beside it, so "measured, none" and "not measured" stay apart in a spreadsheet too.
* **A pin with no answer is still a line**, carrying its error, so the file never drops a pin the board showed as broken.
Text a spreadsheet would run as a formula, such as a prompt that starts with `=`, is written with a leading apostrophe so it opens as text.
## Sharing a board with someone who has no account
**Share** on one of your boards makes a link and copies it. Anyone with the link can open the board without signing in. Our boards cannot be shared; pin what you need to a board of your own and share that.
* The link lasts **180 days** from when it was made. The shared page says until when.
* **Pressing Share again gives back the same link**, so copying it twice never breaks the copy you already sent.
* **Turn off link** stops it at once. Whoever opens it after that is told the board's owner turned it off. Your other shared links keep working: other boards, [client reports](/agencies/client-reports) and [receipts](/account/receipt).
* After you turn a link off, **Share** makes a new link with a fresh 180 days. The old one stays off.
### What the reader sees
The board's name, and each pin's title, chart, numbers and n, drawn exactly as you see them, with no controls. Row labels show as they do for you, so a pin grouped by prompt or page shows those prompts and pages; pin only what you are happy for the link's holder to read.
The reader does not see your workspace, your org, who made the board, or anything about cost or credits. The page is not indexed, and a pasted link previews only as "Shared board" and "A page shared with you."
A shared board's reading is kept for **10 minutes**: everyone who opens the link inside that window sees the same reading. A reading in which a pin could not be read on our side is not kept, so the next open asks again. Any change to the board, such as a pin added or removed or a rename, shows on the next open.
If the link will not open, the page says which of these it is:
| The page says | What happened |
| - | - |
| This link was turned off by its owner | The board's link was turned off, or the board was deleted. Ask for a new link. |
| This link has expired | It is more than 180 days old. Ask for a new link. |
| This link isn't valid | It was copied only in part, or the workspace's shared links were rotated. |
| This board is being opened a lot right now | The link was opened very often in a short time. Try again in a minute. |
| We couldn't load this board just now | The board could not be read at that moment; the link itself may be fine. Reload in a moment. |
### Turning off every shared link at once
Board links are signed with the same workspace secret as receipts, shared rewrites and client reports. **Rotate** on the [receipt](/account/receipt#sharing-a-receipt-without-an-account) stops all of them together, board links included. To give a reader a working link again after a rotation, press **Share** on the board; the new link keeps the old one's expiry date.
## Over the API
Dashboard routes (signed-in session):
* `GET /v2/projects/{id}/boards`: ours and yours, with the limits.
* `POST /v2/projects/{id}/boards` with `{"name": "..."}` makes an empty board.
* `GET /v2/projects/{id}/boards/{boardId}`: the board with every pin asked now. Each pin carries its table (columns, rows, `n`, unit, window and gaps) or its error. `not_connected` lists each source the board reads that this workspace has not connected, and on the Ads board `campaigns` holds the campaign list.
* `PATCH /v2/projects/{id}/boards/{boardId}` renames a board, or reorders its pins with `pin_order` and the `pins_version` you read it at. A reorder against a board someone else changed in the meantime is refused, so reload and reorder again.
* `DELETE /v2/projects/{id}/boards/{boardId}` deletes a board and turns its link off.
* `POST /v2/projects/{id}/boards/{boardId}/pins` with `{title, query, view: {component: "chart" | "table" | "metric", props}}` pins a question. `query` names a `metric`, up to two `group_by` dimensions, the window as `days` or as `from` and `to`, and any filters (`topic_ids`, `prompt_ids`, `engines`, `country` and the like), all at its top level. An unknown field is refused rather than ignored.
* `DELETE /v2/projects/{id}/boards/{boardId}/pins/{pinId}` removes one pin.
* `GET /v2/projects/{id}/boards/{boardId}/export.csv`: the CSV above.
* `POST /v2/projects/{id}/boards/{boardId}/share` returns `{token, url, issued_at, expires_at, expires_in_days}`, the same link while one is live. `DELETE` on the same path turns it off.
Our boards are addressed by name (`overview`, `engines`, `search`, `traffic`, `ads`), and any edit to one answers `405`.
Public route, where the token is the credential:
* `GET /v1/boards/{token}`: the shared board. A turned-off link answers `404`; an expired or invalid one answers `401`; a link opened too often answers `429`.
Boards are not MCP tools.
# Citation rate
Source: https://docs.attensira.com/measure/citation-rate
How often a model attaches a link to your own domain, measured as successful runs citing you divided by all successful runs in the window.
Citation rate is the share of successful runs in which a model attached at least one link to your own domain. It is successful runs citing your domain, divided by every successful run in the window — the same denominator as [share of voice](/measure/share-of-voice), so the two are directly comparable.
## What counts as a citation?
A citation is one URL the model attached to its answer, pointing at a domain you own. Links appear as footnotes, source lists, inline references, or a sources panel depending on the model surface; Attensira normalises all of them to the same thing — a URL, attributed to a domain.
As with mentions, the unit is a **run**. A run where the model links four of your pages counts once toward citation rate. Those four URLs still matter, but they matter to [citations and sources](/measure/citations-and-sources), which counts URLs rather than runs.
```text theme={null}
citation rate = successful runs citing your own domain
÷ all successful runs in the window
```
Failed runs are excluded from the denominator. Runs where the model wrote an answer with no links at all still count in the denominator — a linkless answer is a real answer, and it is a real absence of a citation.
## Why is citation rate lower than share of voice?
Almost always, yes — and the gap is the point, not a bug. Two independent things have to happen for a citation. The model has to decide your brand belongs in the answer, and then it has to decide a page on your domain is worth linking. The first is a condition of the second in practice, so citation rate is bounded from above by how often you are discussed at all.
Several ordinary mechanics widen the gap:
* **Models answer from memory.** A model naming you from training or internal knowledge has no URL to attach. It knows you without having just read you.
* **Not every surface links.** Some model surfaces attach sources on nearly every answer; others rarely do. Your citation rate is partly a property of which [models](/tracking/models) you track.
* **The link may go somewhere else.** A model can recommend you and cite a review site, a marketplace listing, or a comparison article that talks about you. That is a mention with a citation to someone else's domain.
The engine's own framing: **"Mentioned is not cited: a model can name a brand it never links."** A wide gap between your two rates is a legible, actionable signal — you are known but not read.
## What do I do with a wide gap?
Treat share of voice and citation rate as answering different questions. Share of voice asks whether the model considers you part of the answer. Citation rate asks whether your own pages are the material it drew on. A brand with high mentions and low citations is being described through other people's writing; the lever there is publishing pages that answer the prompts directly, and seeing which of your [pages](/traffic/pages) get picked up.
A brand with a low share of voice does not have a citation problem yet. Fix the first number first — no amount of linkable content helps in answers where you are never named.
Before reading a gap as meaningful, check `n` on both rates and check whether the difference cleared the significance gate described in [How we collect data](/measure/how-we-collect-data). Two rates built on a handful of runs can differ by a wide margin purely by chance.
# Citations and sources
Source: https://docs.attensira.com/measure/citations-and-sources
The difference between a mention, a citation and a source, plus how source share and average citation position are calculated.
A **mention** is your name in the answer text with no link attached. A **citation** is one URL the model attached. A **source** is a domain bucket across many citations. Three different objects, three different counts — and confusing them is the single most common misreading of AI visibility data.
## Mention, citation, source: what is the difference?
| Term | What it is | Counted as | Answers |
| - | - | - | - |
| Mention | Your brand named in the answer text, no link required | One per run | Does the model talk about you? |
| Citation | One URL the model attached to an answer | One per URL | Which pages did it link? |
| Source | A domain bucket aggregating many citations | One per domain, per window | Which sites feed the answers? |
In the engine's words: **"Mentioned is not cited: a model can name a brand it never links."** The reverse also happens — a model can cite a page on your domain in an answer that never names you, because the page was useful evidence for something else.
The nesting is straightforward. Many citations roll up into one source. Many runs roll up into one [share of voice](/measure/share-of-voice) or [citation rate](/measure/citation-rate). Rates count runs; sources count URLs. Never compare a rate to a source share and expect them to reconcile — they have different denominators and different units.
## What is source share?
Source share is a domain's slice of all citations in the window: citations to that domain, divided by every citation Attensira recorded across all your tracked runs in that window. Unlike mention and citation rates, source shares **do** describe parts of a whole, because each citation belongs to exactly one domain.
```text theme={null}
source share = citations to a domain
÷ all citations in the window
```
That makes source share a map of the citation landscape around your prompts: which publications, review sites, forums, documentation hubs and competitors the models actually draw on. Your own domain appears in this list like any other. A high source share for a review site means models lean on it when answering your prompts — which tells you where being present is worth the effort.
Source share is sensitive to which prompts and which models you track, since those choose the runs the citations came from. Widen your prompt set and the source mix will shift.
## What does average citation position mean?
Average citation position is the mean index of a URL within the model's citation list, calculated **per source domain**. If a domain's URLs tend to appear as the model's first attached link, its average position sits near the top of the list; if they show up after several others, it sits lower.
Average position is an index into the citation list. It is **not** a rank of your brand, not a search ranking, and not a competitive leaderboard position. Where your brand came in the answer's text is a different number, counted among the names you track; see [Share of voice](/measure/share-of-voice#where-did-you-come-in-the-answer).
Read it as a weak ordering signal and nothing stronger. Models order citations for reasons that are not documented and not stable, and an answer with two citations produces very different index arithmetic than an answer with twelve. Position moves are worth noticing only alongside a change in source share or citation rate; on its own, a shift from 2.4 to 2.1 is not evidence of anything.
## What was each link cited for?
A citation says the answer leaned on a page. It does not say what for. So every citation carries its **context**: the sentence of the answer that carries the link, verbatim. "Acme is the easiest to set up \[2]" beside the review it cites tells you which claim that page is propping up, and that is the claim you would have to answer on that page or elsewhere.
The context is found in the answer's own text, by the link itself, then by its footnote marker (`[2]`, `[^2]`, `【2】`), then by its domain written in the text. The answer's sentences are searched first and its reference lines (a footnote definition, a sources list) only after, because a sources list names the page without saying why it was cited. In a list item or a table row the context is that line. A context is at most 300 characters; a longer sentence is cut to a window around the link, on a word boundary.
The context is never invented. It is always a substring of the answer, and when the text carries no trace of a link, the context is empty:
| What you see | What it means |
| - | - |
| A quoted sentence | The sentence or line of the answer that carries this link |
| "The answer's text does not mention this link." (`context: ""`) | We looked, and the answer's text does not mention or mark this link. The model attached it without saying what it was for |
| — "recorded before we kept what each link was cited for" (`context: null`) | We have not looked. The answer is older than 90 days and was read before contexts were kept |
Contexts are kept for answers from the last 90 days: the stored answers in that window were read again when contexts were introduced, and every new answer gets them as it is read. An older answer shows the dash, not an empty context, because an empty context would claim we looked.
On the prompt page, pick a day and an engine on the day-by-day strip to open that day's answers: each lists its links with their context under **Links and what they were cited for**, beside its brand order. Over MCP and the REST API, each citation on `answer_history` and `answer_slice` rows carries `context` beside `url`, `domain` and `position`.
## How to read these numbers
Like every other number in the product, source and position figures carry sample information. See [Reading the numbers](/measure/reading-the-numbers) for how to handle small samples and unmeasured cells.
# Claims about you
Source: https://docs.attensira.com/measure/claims
The factual statements AI answers make about your brand, quoted verbatim and checked every night against the facts you confirmed in your brand profile. Only confirmed facts count.
An answer can name you, recommend you, and still get your price wrong. Attensira pulls the checkable statements out of every answer and checks the ones about you against the facts **you** confirmed. A wrong one is flagged once, and an automation can act on it.
## What a claim is
A claim is one factual statement an answer makes about a brand: what it costs, what it does or lacks, what it integrates with, a certification, where it is available, or a fact about the company. Opinions are not claims. "Acme starts at \$49 per seat" is a claim; "Acme is the easiest to use" is a stance, and is scored by [sentiment](/measure/sentiment) instead.
Every claim is a receipt, not a paraphrase:
* **The quote is the answer's own words.** A claim is kept only when its quote appears in the answer's text, with spacing normalized. A statement reworded by the model that reads the answer is dropped.
* **It names who it is about.** You (your brand, or a part of your brand family you confirmed), a tracked competitor, or a domain the answer cited. A claim about a brand that resolves to none of these is not kept, because nothing could ever check it.
* **It carries its sources.** The links the answer attached to the sentence the claim is in, when it attached any.
Claims are pulled from an answer in the same read as its [brand order](/measure/share-of-voice#the-full-brand-order), for answers from the last 90 days. An answer that has not been read yet has no claims list, which is different from a list that is empty: an empty list means the answer was read and made no claim we could check.
## Which facts a claim is checked against
Only claims about **you** are checked, and only against facts a person confirmed in your [brand profile](/agent/knowledge-and-skills#company):
* fields in **What you sell**, **Differentiators** and **Proof**, plus any field whose name is about pricing, wherever it is filed;
* that someone on the workspace **confirmed**. A field Attensira drafted from your site is our guess, not your word, and is never used;
* that are not **stale**. A confirmed field whose source page has changed since you confirmed it may no longer be true, and is set aside until it is confirmed again.
With no confirmed fact to check against, nothing is asked and every claim about you is `not_covered`. Confirm your pricing, your integrations and your certifications first: those are the facts an answer gets wrong in a way that costs a sale.
## The verdict
Each claim about you gets one of three verdicts:
| Verdict | Shown as | Means |
| - | - | - |
| `contradicts` | **Wrong** | A confirmed fact shows the claim is false |
| `matches` | **Correct** | A confirmed fact confirms the claim |
| `not_covered` | **Nothing to check against** | No confirmed fact speaks to it |
A claim not checked yet shows **Not checked yet**.
**Nothing to check against is not a wrong claim**, and it is not a right one either. Your facts are silent on it, and silence is not a contradiction. If a claim keeps landing here and it matters, confirm the fact that settles it.
The verdict is a decision, made with a confidence. A contradiction is only treated as a wrong claim when that confidence is **at least 80%**. Below it, the verdict is kept with its confidence and nothing is raised.
### When claims are checked
The check runs **every night at 04:15 UTC**, newest answers first, over the claims whose verdict is missing or out of date. A verdict goes out of date when:
* the answer is read again and its claims change;
* **you confirm, edit or un-confirm a fact.** Every claim checked against the old facts is checked again against the new ones, starting the next night and newest answers first. You never re-run anything by hand.
When a workspace is left with no confirmed fact to check against, every verdict is settled to "Nothing to check against" without asking again, so a contradiction of a fact nobody stands behind any more does not stay on the list.
## The `claim_wrong` event
A wrong claim raises one `claim_wrong` event. It is raised **once per distinct wrong statement against a fact**: the same sentence, said by three engines on ten days, is one event. It is raised again only if the fact it contradicts is edited, because then it is a different question.
The check only flags. It never edits a fact, never files work and never approves anything. What happens next is up to whoever listens for the event.
### Acting on it with an automation
`claim_wrong` is recorded in the same event log as the other [event types](/agent/autopilot#event-automations), but it is not one you can pick yet: the dashboard, MCP and the REST API offer only the five listed there, and the built-in Event responder does not listen for it. Only an event automation saved with an empty event list, which listens for every type, acts on it. Such a run's cause reads: "Because an AI answer said something about you that your confirmed company facts contradict."
The event carries what a run needs without reading anything else:
| Field | What it is |
| - | - |
| `quote` | The claim, verbatim |
| `kind` | `pricing`, `feature`, `integration`, `compliance`, `availability`, `company` or `other` |
| `fact_key`, `fact_label`, `fact` | The confirmed fact it contradicts, and that fact's value |
| `p` | The confidence of the verdict |
| `prompt_id`, `platform`, `surface`, `slot_day` | Where and when the answer said it |
| `reading_id`, `claim_id` | The answer and the claim, for `answer_history` and the receipts |
| `cited_urls` | The pages the answer attached to the claim, when it attached any |
When an automation does act on it, the event limits apply as for every event: at most 10 events per run and 3 runs per automation per rolling 24 hours, and events older than 48 hours are not acted on.
## Where you see claims
* **Over MCP and the REST API**, `answer_history` and `answer_slice` rows carry each answer's extracted `claims`, each with `kind`, `subject` (`self` for you, otherwise a domain), `quote` and `citation_positions`. `null` means the answer has not been read for claims yet; `[]` means it was read and made none. Verdicts are not on the answer rows.
* **`query_evidence`** counts wrong claims with the `wrong_claims` metric, by day or week, prompt field, engine and surface. See [MCP tools](/mcp/tools).
* **In the dashboard**, the prompt page's claims section and the analytics overview's wrong-claims list do not show verdicts yet. Each says so in place of a list, rather than showing an empty one that would read as "nothing wrong".
## What it gets wrong
The verdict is a judgement a model makes against the facts you confirmed, and it can be wrong. It errs toward silence: a contradiction it is unsure about is kept, not raised. It can only be as right as your facts: a confirmed fact that is out of date makes a correct answer look wrong. Keep the facts in your brand profile current, and read the quote before you act on a flag.
# Who decides an answer
Source: https://docs.attensira.com/measure/deciding-sources
For every prompt, on every model, the pages the answers actually leaned on — whether you are on each one, and the next move.
Share of voice and citation rate tell you whether you won a prompt. This page tells you **why** — which pages the answer read, how much weight each one carried, and what to do about the ones that are not yours.
## The grain
One group per prompt, per model, per surface. Surface is in the grouping for the same reason it is everywhere else in the product: a consumer answer and an API answer draw on different pages, and averaging them produces a plan that fits neither.
Country is deliberately **not** in the grouping. A page that decides a prompt in the US decides it in the UK too, and splitting by country here would quarter every sample for no gain in what to do next.
## Weight, not raw citations
Sources are ranked by **citation share × position**, not by how many times a page was cited. A page cited once at the top of every answer decided more than a page cited twice near the bottom of one, and a ranking that could not tell them apart would keep sending you after the loudest source instead of the one that actually decides.
Position weight is `1 / mean position`. A citation the model attached without a clear position is scored as if it sat at the back of the list — stated as such, rather than dropped or quietly treated as first.
## Classification: yours, a competitor's, or someone else's
Every source in a group is classified the same way the [sources report](/measure/citations-and-sources) classifies it, so a domain is never "ours" on one screen and "third party" on another:
* **Yours** — a page on your own domain.
* **A competitor's** — a page on a domain you track as a competitor.
* **Someone else's** — everything else: review sites, forums, documentation hubs, comparison pages, listicles.
## "You are on it"
A source is marked **on it** in either of two cases:
* it is your own domain, or
* you have a placement there whose execution status is **verified**.
An unverified or unknown placement is never shown as on it. This is the same discipline as everywhere else in the product: a claim without evidence does not get to look true.
## The next move
Every source carries two things: **where you stand on it**, and **what kind of source decides answers on that engine**. Together they say what to do next.
### Where you stand: new, better or fix
Every source in a group gets one of three words. Only the group's top ten sources are looked at when deciding whether a competitor ranks above you:
| Word | When |
| - | - |
| `new` | You are not on it. Nothing of yours is there to be cited yet. |
| `better` | You are on it, and a competitor's domain in the top ten ranks above it. |
| `fix` | You are on it, and no competitor in the top ten ranks above it. The work is the page itself. |
The group gets the same words, read off your best-ranked source: `new` when nothing of yours is in its top ten, `better` when a competitor ranks above your best, `fix` otherwise.
### On the Sources tab: one move per domain
**Analytics → Sources** has one row per domain for the whole workspace, so the per-group words are folded into four columns:
* **Type**: what sort of place the domain is (see below).
* **You're on it**: your own site, or a placement we verified.
* **Prompts decided**: the tracked prompts where this domain is in the top ten sources on at least one engine. A 0 is a measurement: the domain was cited, and never high enough to decide.
* **Next move**: what to do about this domain.
A domain that decides several prompts can have a different word on each. The move with the most work in it wins: `better` over `new` over `fix`. A domain we are ahead on for one prompt and behind a competitor on for another needs the `better` work.
The next move is the word and the type together, said as a verb:
| Where you stand | Type | Next move |
| - | - | - |
| `new` | Reddit, Community | Reply in the thread |
| `new` | G2, Directory | Get listed |
| `new` | Media | Pitch |
| `new` | Competitor | Publish our answer |
| `new` | Reference, AI platform | Get cited |
| `better` | any | Improve our entry |
| `fix` | any | Fix our page |
A domain that is not in the top ten sources for any prompt in the window has no move. Its cell is a dash, and hovering it says why.
The types:
* **Yours**: your own domain, or another domain confirmed as yours.
* **Competitor**: a domain you track as a competitor, whatever kind of site it is.
* **Reddit** and **G2**: each its own type, because each has its own way in.
* **Directory**: review sites and listing directories such as Capterra, TrustRadius, Trustpilot and Product Hunt, where there is a form to fill in rather than an editor to pitch.
* **Community**: social and user-written sites other than Reddit, such as YouTube, Quora, LinkedIn, Medium and GitHub, where the author is not the site.
* **Media**: the rest of the third-party web: publishers, blogs, listicles and trade press.
* **Reference**: institutional sources such as Wikipedia, `.gov` and `.edu` domains.
* **AI platform**: an assistant or AI tool's own site.
The type is inferred from the domain and cannot be corrected yet.
**The pill does not start anything.** Pressing it opens a chat with the move written in: the domain, how often it was cited, how many prompts it decides, whether you are on it, and the move, with a request to the agent to plan it and do the work, asking you only for what it cannot find itself. Nothing is sent until you press send.
### What decides on each engine
Alongside the word, every source and group carries what kind of source decides answers on that engine, and the move that works there. Today, every workspace gets this from one static, per-engine table, because the observations layer this could instead draw on does not have enough evidence yet:
| Engine | What decides answers there | The proven move |
| - | - | - |
| ChatGPT | Your own docs and pricing pages | Fix your own page |
| Claude | Review sites and listicles | Get listed |
| Perplexity | Threads, YouTube and roundups | Be in the thread or roundup |
| Gemini | Threads, YouTube and roundups | Be in the thread or roundup |
| Google AI Overview | Threads, YouTube and roundups (closest documented behavior) | Be in the thread or roundup |
An engine not in the table gets the threads and roundups row. Once your own workspace has read enough answers on a given source (at least 20 successful reads), this switches from the static table to what your own history shows. It always says which one it came from, so you know whether you are looking at a general rule about the engine or a pattern proven in your own data.
Every row and every group carries **n**: the successful answers behind it, in the same window and counted the same way as the rest of the product. A share without its n is noise in a confident font.
## Where to find it
* On **Analytics → Overview**, each lost prompt names the source that decided it. See [The analytics overview](/measure/analytics-overview#where-are-we-losing-and-to-whom).
* On **Analytics → Sources**, in the **Type**, **You're on it**, **Prompts decided** and **Next move** columns.
* In a prompt's [win plan](/measure/win-this-answer), engine by engine.
* As the agent tool **`deciding_sources`**, so a chat or automation can pull the same map, as the same-named [MCP tool](/mcp/tools#deciding_sources) from your own assistant, and over REST as `GET /v1/deciding-sources`. Each takes `prompt` to narrow to one prompt and `days` to set the window.
```json MCP theme={null}
{
"name": "deciding_sources",
"arguments": { "prompt": "prm_9c2", "days": 30 }
}
```
See [Reading the numbers](/measure/reading-the-numbers) for how to treat `n`, `null`, and significance everywhere else these shapes appear.
# Goals
Source: https://docs.attensira.com/measure/goals
A goal is one number from a fixed catalog, a target and a date. Attensira measures where it starts, reads it daily by 06:00, and says whether it is ahead, on pace, behind or unmeasured with the same arithmetic everywhere.
A goal is one number Attensira can read, a target for it, and a date: *named in 30 of 60 prompts on ChatGPT by 31 December*, *2,400 Search Console clicks over 28 days by 31 March*. You say what you want in your own words; the agent turns it into a goal you can measure, and you decide whether to create it. After that the readings, the pace and the re-planning are the agents' job.
Every goal is held to three rules:
* **Where it starts is measured, never typed.** The baseline is the first successful reading Attensira takes after the goal is created. No person, assistant or model can set it.
* **A number that could not be read is never a 0.** The goal reads as **unmeasured**, with the reason ("Connect Search Console").
* **A goal reports where a number stands, never why.** Our shipped changes appear beside the chart as markers. No goal sentence says a number moved *because* of our work, and nothing is ever described as won, driven or earned.
## Setting a goal from your own words
On **Analytics → Goals**, type what you want into **Add a goal in your own words**, for example *Double demo requests from search by March*, and press **Propose a goal**. A chat opens in goal mode, and the Chief of staff takes it from there:
1. It reads the goals you already have, so it does not propose one twice.
2. It picks the one metric from the [catalog](#the-metric-catalog) that measures what you described, scoped to the assistant, topics or keyword you named. If the words are ambiguous (which event is a demo request, which assistant), it asks first, at most three questions.
3. It proposes the goal as a card. The card shows the metric, where the number comes from, **today's value with its `n`** (read from the source when the card is drawn, and not stored), the target and date, and the pace line between them.
4. You press **Create goal**, or **Not now**. Nothing exists until you press Create. Accepting the same card twice creates one goal.
If the metric's source cannot be read in your workspace (Search Console not connected, the AI traffic snippet not installed, Google Analytics or Stripe), the card says why and offers no Create. The Chief of staff offers the nearest metric that can be read, called a signal, and proposes it only if you agree.
The same goal mode starts from **Propose the next** on a goal that was hit or missed. Typing `/goal` in the chat composer does not start it yet.
The goal chat is a chat like any other and spends [credits](/account/credits) the same way. Setting a goal, editing it and its daily readings spend none, and they keep working when the credit balance is spent.
### Starting goals at signup
During setup, Attensira proposes starting goals from what the first scan found: **become visible** on each AI assistant you left on, **be recommended** against the competitors you confirmed, and **grow clicks from Google Search** once Search Console is connected and Google Search is on. Each has a date 90 days out. You keep or switch off each one. The ones you keep are created when your plan starts, with each target set so it is never below where the goal starts.
## The metric catalog
A goal measures exactly one metric from this list. A metric outside it is refused, and so is a parameter the metric does not take: a goal that silently ignored *only the pricing topic* would read a different number from the one you asked for.
| Metric | What it counts | One reading covers | Better | Parameters |
| - | - | - | - | - |
| `ai_named_prompts` | Tracked prompts the assistant names you in, out of those in scope | The latest answer per prompt in the last 7 days | Higher | `platform` (required), `topic_ids` |
| `ai_share_of_voice` | [Share of voice](/measure/share-of-voice): answers that name you, out of every successful answer in scope | The last 7 days | Higher | `platform`, `topic_ids` |
| `ai_citation_rate` | [Citation rate](/measure/citation-rate): answers that cite your domain, out of every successful answer in scope | The last 7 days | Higher | `platform`, `topic_ids` |
| `google_keywords_top10` | Tracked Google keywords where you rank in the top 10, out of those read | The latest position per keyword in the last 7 days | Higher | `keyword_ids` |
| `google_keyword_position` | Your Google position for one keyword | The latest position in the last 7 days | Lower | `keyword_id` (required) |
| `search_clicks` | Search Console clicks | 28 days ending 4 days ago | Higher | — |
| `search_impressions` | Search Console impressions | 28 days ending 4 days ago | Higher | — |
| `ai_agent_fetches` | Fetches of your pages by AI systems, from the [AI traffic snippet](/traffic/ai-traffic) | 28 days ending 2 days ago | Higher | — |
| `ai_referred_visits` | Visits referred by AI assistants, from the AI traffic snippet | 28 days ending 2 days ago | Higher | — |
| `ga4_key_events` | Google Analytics key events | 28 days ending 2 days ago | Higher | `ga4_event` (required), `ga4_channel` |
| `ga4_sessions` | Google Analytics sessions | 28 days ending 2 days ago | Higher | `ga4_channel` |
| `stripe_new_customers` | New Stripe customers | The last 28 days | Higher | — |
* `platform` is one AI assistant your workspace tracks: `chatgpt`, `perplexity`, `google_ai`, `claude` or `gemini`. Without it, a share or rate goal reads across every tracked assistant. See [Models](/tracking/models).
* `topic_ids` narrows an AI goal to prompts in those [topics](/tracking/topics), at most 50. Every id must be a topic of this workspace.
* `ga4_channel` is `organic`, `ai_referred` or `all` (the default).
* An AI goal counts the same prompts the [Prompts](/tracking/prompts) board shows for that assistant, with the same rule for what *named* means. A prompt whose every read failed is in the whole it counts out of, never counted as a miss.
### Business signals
Four metrics are **business signals**: `ai_referred_visits`, `ga4_key_events`, `ga4_sessions` and `stripe_new_customers`. They are shown beside our work and never credited to it: a signal's sentence opens with *A business signal:*, and nothing in the product turns one into revenue we drove. There is no revenue-in-currency metric.
### What each workspace can read today
The catalog is the same for everyone. Whether a metric can be read is per workspace, and the metric picker, the proposal card and the API all say which and why:
| Source | Readable when | Otherwise the goal says |
| - | - | - |
| AI answers we read | Always | — |
| Google rank tracking | The workspace has tracked Google keywords | *No Google keywords are tracked yet* |
| Search Console | Search Console is [connected](/account/connections) and its history has been pulled | *Connect Search Console*, *Search Console needs reconnecting*, or *Search Console history hasn't been pulled yet* |
| AI traffic snippet | The snippet has reported at least once | *Install the edge snippet* |
| Google Analytics | Not yet: there is no Google Analytics connector | *Google Analytics isn't a connector yet* |
| Stripe | Not yet: there is no Stripe connector | *Stripe isn't a connector yet* |
A goal on a source that cannot be read can still exist (over the API, for example). It reads as unmeasured with that reason every day, and starts measuring the first morning its source can be read.
## Targets, dates and limits
| Rule | Value |
| - | - |
| Target, for a count | A whole number, at least 1 |
| Target, for a share or rate | Between 0 and 1: `0.4` is 40% |
| Target, for a Google position | A whole position from 1 to 100 |
| Target date | 7 to 400 days after the goal starts, and after today, as a `YYYY-MM-DD` day in your workspace's time zone |
| Name | At most 120 characters. Without one, the goal is named after its metric |
| Goals per workspace | At most **12** that are not archived. A 13th is refused with `409` until you archive one |
A goal's metric and parameters never change: a different metric is a different goal. You can change its name, target, date and status. A new target must beat the measured baseline.
## The first reading and the daily reading
**The first reading** is queued the moment a goal is created and lands within about 15 minutes. Until then the goal says *First reading by 09:15* (the time in your workspace's zone). Its first reading that succeeds becomes the **baseline**, the number the goal starts from. If its source cannot be read yet, the baseline waits for the first morning it can.
**Every morning**, from 04:00 on your workspace's own clock, Attensira reads each active goal once for that day. The readings normally land before the morning goal-pace check at 05:00, and always by **06:00**, ahead of the daily win plan. Each goal has at most one reading a day; a repeated or retried read is harmless.
A reading is one of:
| Reading | Means |
| - | - |
| **ok** | A value, with its `n` (prompts, answers, keywords or days behind it) and, for a count, the whole it is out of |
| **unavailable** | The source could not be read for a known reason, which the reading carries. No value, and never a 0 |
| **error** | The read was tried and failed. The next pass tries again |
A Google position reading that finds you outside the top 100 is a measured reading (*not in the top 100*), worse than any position, never unmeasured.
Only active goals are read. A goal that is hit, missed, paused or archived is not read until it is active again, so its chart stops at its last reading. Goal readings are part of the monitoring the platform runs, and spend no credits.
## Pace: ahead, on pace, behind or unmeasured
Every reading recomputes the goal's pace from stored readings, with fixed arithmetic. No model decides whether a goal is on pace, and the Goals page, the chip, the morning brief, the board report, MCP and the API all show the same stored verdict.
```text theme={null}
start = the later of the goal's start day and its baseline's day
f = (today − start) ÷ (target date − start), clamped to 0..1, in whole local days
expected = baseline + (target − baseline) × f the pace line
current = mean of the ≤ 3 newest ok readings in the last 7 days
(the median, for a Google position)
band = the noise band for the metric's unit, below
ahead current beats expected by more than the band
behind current trails expected by more than the band
on pace anything inside the band
```
"Beats" and "trails" follow the metric's direction: for a Google position, lower is better.
The pace line starts at the baseline's own day when the first good reading came after the goal was set (a source connected weeks later), so a goal never reads as behind for days nothing could measure.
The band keeps day-to-day noise from flipping the verdict. It is computed from the newest reading's sample:
| Unit | Band |
| - | - |
| A count out of N (prompts named, keywords in the top 10) | `max(1, √(N × p × (1 − p)))`, where `p = expected ÷ N`, held between 0.05 and 0.95 |
| A share or rate with `n` answers | `max(0.02, √(p × (1 − p) ÷ n))`, where `p = expected` |
| A 28-day count (clicks, impressions, fetches, visits, events) | `max(1, √expected)` |
| A Google position | 1 |
A goal is **unmeasured** when there is nothing honest to compare, and always says why:
| Reason | Means |
| - | - |
| *First reading by 09:15*, *Waiting for the first reading* | No baseline yet |
| The source's own reason (*Connect Search Console*) | The newest reading in the last 7 days could not be read |
| *No reading in the last 7 days* | Nothing was read in the window |
| *paused* | You paused the goal |
### The sentence
Each goal carries one sentence, written by the server and shown unchanged everywhere. It is built from counts with their `n`, places the value against the pace line, and never says why the number moved:
> Named in 21 of 60 prompts on ChatGPT (n = 58). 19 expected by today, so on pace for 36 by 30 Nov.
> Cited in 14% of AI answers on Perplexity against 10% expected by today (n = 180), so ahead of pace for 20% by 31 Dec.
> Search clicks: not measured yet. Connect Search Console.
A share read from a single answer is not a share, and says so: *too few answers read to give a share yet (n = 1)*.
## Hit, missed, paused and archived
| Status | How a goal gets there | How it leaves |
| - | - | - |
| **active** | Created, or re-opened | — |
| **hit** | A reading finds the smoothed current value at or past the target. It is not read again while hit | Raise the target, and the goal is active again |
| **missed** | Its date passes without being hit. It is not read again while missed | Move the date later, and the goal is active again |
| **paused** | You pause an active goal. It is not read while paused | Resume it |
| **archived** | You archive it, from any status. Archived goals leave the Goals page and the lists, and cannot be edited | Final |
Hit and missed come only from readings, and stay until you edit the target or the date. When a goal is hit or missed, a row appears in your [Inbox](/agent/inbox): *Goal hit: … Propose the next?* or *… missed its date. Move the date or propose the next?* Its button opens a chat with the ask filled in, where the Chief of staff proposes the next goal or a new date for you to accept.
## When a goal falls behind
At 05:00 on your workspace's clock, after the readings, Attensira looks at every active goal whose pace is **behind** and decides whether the gap is worth re-planning the work:
* More than **30%** of the climb from baseline to target behind, with fewer than **14 days** left, always counts.
* Otherwise the agent weighs the numbers (the gap, the days left, the readings with their `n`, the work already shipped and waiting for its [re-read](/measure/before-and-after), the last re-plan) and re-plans only on a confident yes.
What happens next follows your [approval mode](/agent/approval). In **Draft** or **Act**, one re-plan run of the daily win plan starts, named for the goal (*Re-plan: … is behind pace*), and the goal's chip reads **Behind, re-planning**. It is an ordinary run under your approval mode and change caps. In **Ask**, nothing starts: one Inbox row offers the re-plan (*… is behind pace: re-plan the work toward it?*).
A goal is re-planned at most once a week. A re-plan you cancelled, or an offer you dismissed, is not raised again that week. The check never edits a goal, a target or a setting.
## When a source breaks
If a connection the readings use stops answering (Search Console's grant was revoked), the Inbox gets one **Reconnect Search Console** row naming the goals waiting on it. It is the same row the connector's own health check raises, so you see one, not several. The next reading that succeeds closes it. *Not connected* and *not a connector yet* are states the goal shows, not failures, and raise nothing.
## Where goals show up
* **Analytics → Goals**, the first Analytics tab. Each goal is a tile with its current value, target, date, pace chip, sentence and source. Opening one shows its daily readings against the pace line, the days our changes went live as markers, and the controls you steer with: target, date, pause or resume, archive. A day that could not be read stays on the chart with its reason. Edits name the version you were looking at; if the goal changed since you opened it, the sheet reloads it and says so.
* **The Analytics overview**: each goal as a chip with its value and target.
* **The Monday [morning brief](/agent/morning-brief)**: a status line counting goals by pace, and each goal's sentence.
* **The board report**: each goal's pace for the period. See [client reports](/agencies/client-reports).
* **Your [Inbox](/agent/inbox)**: hit, missed, behind (in Ask mode) and reconnect rows.
* **Your assistant and your code**: below.
## From your assistant or your own code
The [MCP server](/mcp/overview) has the goal tools below, and the [REST API](/api/overview) has their twins:
| MCP tool | REST | Does |
| - | - | - |
| `get_goals` | `GET /v1/goals` | The workspace's goals, oldest first. Archived goals are left out. Over REST only, `?status=` takes a comma-separated list of `active`, `hit`, `missed`, `paused`, `archived`, or `all` |
| — | `GET /v1/goals/metrics` | The catalog as this workspace sees it: each metric's key, label, source, unit, direction and parameters, whether it can be read here (`available`, with `reason` when not), and the AI platforms the workspace tracks |
| `set_goal` without `id` | `POST /v1/goals` | Sets a goal: `{name?, metric, params?, target, target_date, work_area_id?}`. Answers `201` |
| `set_goal` with `id` | `PATCH /v1/goals/{id}` | Edits a goal: `{version?, name?, target?, target_date?, status?, work_area_id?}`. `status` is `active` (resume), `paused` or `archived`; `work_area_id: null` moves the goal to the whole workspace |
The writes need write access: a read-and-write API key, or an OAuth connection. They spend no credits and work when the credit balance is spent. Call `set_goal` only on the person's instruction.
* **The baseline is not a field.** A body carrying `baseline` is a `400`, and so is an edit that tries to change `metric` or `params`.
* **Retries.** Send an `Idempotency-Key` header (`idempotency_key` on `set_goal`) with a create: a retry answers `200` with the goal the first call set. The same key reused for a goal on a different metric is a `422` carrying that goal.
* **Versions.** Without `version`, an edit applies to the goal as it is now. With one, an edit of a goal that changed since you read it is a `409` carrying the goal as it now stands (`details.goal`).
* **Limits.** Past 12 goals that are not archived, a create is a `409` with `{limit}`.
Each goal comes back with:
| Field | Notes |
| - | - |
| `id`, `name`, `metric`, `metric_label`, `params` | What it measures |
| `source`, `source_status` | Where the number comes from, and `{available, reason}` for this workspace today |
| `unit`, `direction`, `signal` | `count_of`, `rate`, `rolling_count` or `position`; `up` or `down`; `signal` is `true` for a business signal |
| `baseline`, `current` | `{value, n}`. `value` is `null` when nothing was measured, never `0` |
| `target`, `target_date`, `start_day` | In the metric's unit; days are `YYYY-MM-DD` in the workspace's zone |
| `pace` | `{state, expected: {value}, reason}`. `state` is `ahead`, `on_pace`, `behind` or `unmeasured`; `expected.value` is the pace line today, `null` while unmeasured |
| `sentence` | The server's sentence. Quote it rather than writing your own |
| `status`, `status_reason` | `active`, `hit`, `missed`, `paused` or `archived` |
| `replanning` | `{since, session_id}` while a re-plan from the last 7 days is under way, else `null` |
| `scope`, `work_area_id`, `work_area_ids` | `workspace`, or the project that owns the goal; and every project serving it |
| `created_via` | `person`, `chief_of_staff`, `onboarding` or `assistant` |
| `version`, `created_at`, `updated_at` | Name `version` on an edit to refuse a stale one |
A goal set over MCP or the API is recorded as set by your assistant (`created_via: "assistant"`), on behalf of the key's person.
`set_goal` takes `name`, `metric`, `target`, `target_date` and `work_area_id`, and nothing else. It sends no `params`, so a metric that needs one (`ai_named_prompts` needs `platform`, `google_keyword_position` needs `keyword_id`, `ga4_key_events` needs `ga4_event`) is set with `POST /v1/goals`, or from your own words in the app. Pausing, resuming and archiving (`status`) and naming a `version` are REST only.
### Webhooks
Every transition sends one [`goal.updated`](/api/webhooks#events) event: a goal set, edited, paused, resumed or archived, or a reading that moved its pace or status. A daily reading that changes neither sends nothing, and a pace that flips more than once in a local day announces only the first flip. The event carries the pace state, never the numbers behind the band.
## What goals do not do yet
* **Google Analytics and Stripe cannot be read.** Their metrics are in the catalog so a goal can name them; every such goal reads as unmeasured with the reason.
* **Keyword-scoped Google goals cannot be set yet.** A goal that names keyword ids (`keyword_id`, or `keyword_ids`) is refused with *keyword tracking isn't set up for this workspace yet*. `google_keywords_top10` across every tracked keyword can be set.
* **No forecast.** The pace line is a straight line from where the goal started to its target. Attensira says where a number stands against it, never where the number will land.
# Google search
Source: https://docs.attensira.com/measure/google-search
What Attensira records from Google: Search Console clicks, impressions and positions kept day by day with a 16-month backfill, and daily rank tracking with the AI Overview on each results page.
Attensira keeps two records of how your site does in Google, beside what the AI answer engines say about you:
* **Search Console**: what Google counted for your own site. Clicks, impressions and average position, per page and per search query, for every day Google reports.
* **Rank tracking**: where your site ranks for a list of tracked Google searches, read once a day. Each reading also keeps who else is on the page and whether Google showed an AI Overview.
Both are stored day by day, so you can compare a page's Google numbers with its AI citations, and both have the same rules as every other number in Attensira: a number nobody measured is `—` with a reason, never `0`. See [Reading the numbers](/measure/reading-the-numbers).
## Connecting Search Console
Search Console is a [plugin](/agent/plugins). Connect **Google Search Console** and choose the property for this workspace's site. Until it is connected, nothing is pulled and every Google column reads `—` with the reason *Search Console is not connected for this workspace*, never a zero.
### The first pull: sixteen months of history
Google keeps sixteen months of Search Console data and drops the oldest day every day. A day that falls off the end cannot be read again by anyone.
So the first time Attensira finds the connection, it pulls the whole sixteen months, one day at a time, oldest first: the oldest days are the ones about to be lost. The pull is paced at up to 30 days an hour per workspace, so a full history takes most of a day to arrive. It saves its place after every day, so an interruption costs at most the day it was on.
Once a day is stored, Attensira keeps it. Your history keeps growing past Google's own sixteen months.
If you switch the connection to a different property, the pull starts again from sixteen months back. Days from the old property are not mixed into the new one: until the new pull reaches them, they are not counted.
### Every day after that
Google reports Search Console with a lag of a few days, and a day's first figures still change for a day or two after that. Once a day Attensira pulls the day three days back and pulls the two days before it again, which is where a provisional day settles into its final numbers. A re-pull replaces the day; it never adds to it.
Days missed while the connection was broken are filled in on later passes, oldest first, up to two weeks of them per pass.
The newest days of any window are usually not reported yet. They read as *not recorded*, not as days with no clicks.
### When the connection breaks
If Google stops answering for your property, two things happen:
* An **Inbox** item, *Reconnect Search Console*, explains what is not being recorded. Reconnecting closes it, and the missed days fill in on their own.
* The Search Console lines on [Loop health](/agent/loop-health) show the pass as failed, with the reason.
Reconnect promptly. A day that falls past Google's sixteen months while the connection is broken is gone.
## Page totals and query detail
Search Console data is stored at two grains, and they are not interchangeable.
| Grain | One row per | Complete? |
| - | - | - |
| **Page totals** | page × day | Exact. Every impression Google counted for the page, including impressions for queries Google anonymises |
| **Query detail** | search query × page × day | The top 5,000 rows of each day by impressions. Anonymised queries are not in it at all |
Google leaves anonymised queries (rare or personal searches) out of any report that lists queries. Adding up query detail by page therefore **under-counts** every page that gets them, often by a large share. That is why page totals are their own pull and are never computed from query detail. Every per-page number in Attensira, including the **Google position** and **Google clicks** columns on the [Pages](/traffic/pages) grid, comes from page totals.
Query detail is what you read when you ask about a search query. When a day had more rows than are kept, that day is marked truncated, and any number by query that includes it is a floor: the queries kept are the top ones, not all of them.
### Position
Position is Google's average position, weighted by impressions: the same figure Search Console shows for the same pages and days. A row Google reported without a position adds its clicks and impressions but nothing to the average. A page with no impressions in the window has no position, `—`, never 0 or first.
### Which days count
A window counts only the days Attensira holds a pull of. Within those days, a page Google did not show at all is a measured 0 clicks and 0 impressions. Outside them (before the backfill reached them, or newer than Google has reported) the days are *not recorded*, and an answer that touches them says so.
## Rank tracking
Rank tracking reads Google's results for a list of tracked searches once a day.
### Which searches are tracked
The list is seeded from Search Console: each day, the searches that brought your site at least 10 impressions over the last four weeks of stored days, most impressions first, fill up to 50 slots. A workspace tracks at most 100 searches. Without Search Console there is nothing to seed from, so rank tracking starts once Search Console is connected and reporting.
Each search runs as a desktop Google search in your workspace's country and its language. The countries with their own location today are the US, UK, Canada, Australia, New Zealand, Ireland, India, Germany, France, Spain, Italy, the Netherlands, Sweden and Estonia. A workspace in any other country is searched from the US, in English, and every reading records the location it was searched in.
### What one reading holds
* **Your position**: the best organic position of any page on your domain in the first 30 results. Subdomains count (`blog.example.com` counts for `example.com`); a lookalike domain does not. If you are not in the first 30, there is no position: `—`, never 0.
* **Your ranking page**: the URL that held that position.
* **The top ten**: the first ten organic results, with each one's domain, URL and title, so you can see who outranks you.
* **The AI Overview**: whether Google showed one, which pages it cited, and whether any of them is yours.
### When a reading fails
A search that fails, is refused, or has not come back within 24 hours is stored as **unavailable**, with the reason in words. It has no position and no AI Overview reading. A missing reading is never written as a position, and it is never read as "not ranking".
### AI Overview: present, absent or unknown
Whether Google showed an AI Overview has three values, because "there was none" and "we could not see one" are different findings.
| Value | Meaning |
| - | - |
| `true` | Google showed an AI Overview. Its cited pages are kept |
| `false` | We asked Google's results to load the AI Overview, and none came back |
| `unknown` | The reading cannot say. Every unavailable day is `unknown` |
The share of days with an AI Overview counts only `true` and `false` days. Days that are `unknown` are left out of the share, not counted as absent, and the answer says how many there were.
## Day basis
Google's numbers and the AI answer engines' numbers are counted on different calendars, and every answer from `query_evidence` names which one in `day_basis`:
| `day_basis` | Calendar | Used by |
| - | - | - |
| `slot_day` | Your workspace's own day, the local day a prompt is read on | Share of voice, citations, positions in answers, deciding sources, claims |
| `search_console` | Google's own report day. Google counts Search Console days in Pacific Time | `gsc_clicks`, `gsc_impressions`, `gsc_position` |
| `utc` | The UTC calendar day | Rank tracking, AI traffic |
The same date label on two bases does not cover the same 24 hours. Compare trends across them by week or by window, not day against day.
## Asking for these numbers
The agent, and you over [MCP](/mcp/tools#query_evidence) or the [REST API](/api/endpoints#post-v1evidencequery), read the stored history with `query_evidence`:
| Metric | Value | `n` is | Group by |
| - | - | - | - |
| `gsc_clicks` | Clicks, a count | Impressions | `day`, `week`, `page`, `keyword` |
| `gsc_impressions` | Impressions, a count | Impressions | `day`, `week`, `page`, `keyword` |
| `gsc_position` | Average position | Impressions that carried a position | `day`, `week`, `page`, `keyword` |
| `serp_position` | Average of your daily positions | Days you ranked | `day`, `week`, `keyword`, `page` |
| `ai_overview_presence` | Share of days with an AI Overview, 0 to 1 | Days with a known answer | `day`, `week`, `keyword`, `page` |
For the Search Console metrics, `keyword` is the search query and reads query detail; without it, totals come from page totals. For rank tracking, `keyword` is the tracked search and `page` is your ranking page. A `page` key is the URL without its scheme, a leading `www.` or a trailing slash (`acme.com/pricing`), so the spellings Google and your site use for one page land on one row. These metrics take no filters other than the window.
What could not be measured comes back in `gaps`, by reason:
| Reason | Meaning |
| - | - |
| `not_connected` | Search Console is not connected for this workspace |
| `not_recorded` | These days have not been pulled yet: Google has not reported them, or the backfill has not reached them |
| `truncated_day` | Query detail on this day kept only the top rows, so numbers by query are a floor |
| `truncated` | The question had more groups than one answer reads; the ones with the most impressions are kept |
| `no_impressions` | A Search Console position over no impression that carried one |
| `not_populated` | No tracked search was read in this window |
| `not_ranked` | You were not in the results read, so there is no position |
| `unavailable` | Rank-tracking days that could not be read |
| `ai_overview_unknown` | Days that could not say whether an AI Overview was shown, left out of the share |
The agent's own live Search Console look-up, through the plugin, reads at most 100 rows a call. The daily pull and the backfill are not something an agent runs: they are the stored history, and `query_evidence` is how an agent reads it.
## Related
Every page across AI answers and Google, side by side.
How a shipped change is re-read, and the labels its result reads in.
Why `—` and `0` are different findings.
Where a failed or blocked pull shows up.
# How we collect data
Source: https://docs.attensira.com/measure/how-we-collect-data
The full measurement methodology: which model surfaces are read, how often, how deeply we sample, and the significance test every delta has to pass.
Attensira sends your prompts to real model surfaces on a fixed daily cadence, records what comes back, and computes every rate over **runs** — one prompt, one model, one country, one point in time. This page documents that pipeline end to end, including where it is thin, so you can judge the numbers yourself.
## Which model surfaces do we read?
A **model** in Attensira is a model surface, addressed by a stable wire id called a channel: `chatgpt`, `perplexity`, `google_ai`, `claude`, `gemini`. The channel survives the provider swapping the underlying model beneath it, so your history stays continuous when a vendor ships a new version.
**We read the surface your buyers actually use, not the API behind it.** Where a consumer surface can be read, that is the primary path: we visit chatgpt.com, perplexity.ai, Google's AI Overview and Gemini the way a person would. The model APIs are a **labelled fallback**, used only where no consumer surface can be read.
This distinction is the difference between measuring the product and measuring a proxy for it. Asking a model through its API is not what someone sees when they type into chatgpt.com: different model version, different system prompt, different retrieval. A visibility number built on the API answers a question nobody asked.
Consumer-surface reading covers `chatgpt`, `perplexity`, `google_ai` and `gemini`. API readers exist for `chatgpt`, `perplexity`, `claude` and `gemini`. **`google_ai` has no API at all** — it is readable only through the consumer surface, which is precisely why AI Overview visibility was unmeasurable before.
Every run records which surface produced it, `consumer` or `api`, and the two are never averaged together. A window spanning both reports as `mixed` rather than silently blending them.
A new workspace tracks four channels by default: `chatgpt`, `perplexity`, `claude` and `google_ai`. On Growth and above you switch `gemini` on yourself in [Models](/tracking/models). A model you have not configured is never queried, and its metrics come back `tracked:false` rather than as a zero — see [Reading the numbers](/measure/reading-the-numbers).
An **agent model** is a different concept: it is the LLM your [chats](/agent/chats) run on. It has nothing to do with the model surfaces measured here.
## Which country is a prompt asked from?
Every run records the country it was asked for: your workspace's country, or the one a topic, a prompt or a [market](/tracking/markets) sets. How the run is located there depends on the surface, and the two ways are not the same thing:
* **Consumer surfaces** (`chatgpt`, `perplexity`, `google_ai`, `gemini`) are read from a browser in that country, so the answer is the one a person there sees. This holds in the [42 countries that can be read locally](/tracking/markets#which-countries-can-be-read-locally); a workspace country outside them is still read, but its answers may not be local.
* **API reads**, which is every `claude` run and any API fallback, are not located anywhere. The country is stated at the end of the question, as `User location: DE`. That tells you how the model answers someone who says where they are. It is not a local search.
A Claude rate is therefore never a reading taken from a country, in your primary market or any other. In a secondary market, a run whose location was only stated is labelled that way and is never compared with a local one. A consumer surface that cannot be read from a secondary market's country is not read at all, and is shown as not measured rather than as zero. See [Markets](/tracking/markets#how-is-each-model-located-in-a-market) for the full table and the countries that can be read locally.
## How often does a prompt run?
An in-process ticker runs hourly and enqueues due work. That hourly tick is a scheduling heartbeat, not a measurement frequency. The rule that governs your data is the one below it:
**Each prompt × model × country combination runs at most once per day, on that prompt's own slot.**
A prompt's slot is not the calendar day rolling over at midnight — it is roughly twenty-four hours after the slot before it, opening at the time of day the prompt was first tracked, in your workspace's own zone. Two prompts added minutes apart can settle into different slots, and that is by design: every count in the product is keyed to a prompt's own slot, not to the clock, so what matters is that each prompt is read at most once per slot, not that every prompt in the workspace is read at the same moment. Twenty prompts across two models in one country still produce forty run-slots a day; they just do not all open together.
## How deep is the sample?
Sampling depth is the number of runs issued per prompt × model × country, per day. It is set by plan:
| Plan | Sampling depth per day | Issued |
| - | - | - |
| Starter | n = 1 | — |
| Growth | n = 3 | Concurrently |
| Business | n = 3 | Concurrently |
Growth, Business and Agency runs are issued **concurrently**, not spaced through the day. Sending them together means all three see the same model state, so the variation you observe is the model's own answer-to-answer variance rather than a change in the world between morning and evening.
Runs that fail — a timeout, a blocked request, a malformed response — are **stored but excluded from n**. They are kept for debugging and never enter a numerator or a denominator. An outage therefore shrinks your sample rather than depressing your rate.
## Why are rates computed over runs?
Every rate in the product uses runs as the unit, never readings, answers, sentences or keyword occurrences. A run either named you or it did not; it either cited your domain or it did not. An answer that names you nine times is one run in the numerator, exactly like an answer that names you once.
This is what makes rates comparable across prompts, models and windows: the denominator is always a count of questions actually asked and successfully answered.
## What is the significance gate?
Every delta Attensira shows has passed a **two-proportion z-test at 95% confidence**. The current window's rate and the prior window's rate are compared as two proportions with their own sample sizes. If the difference does not clear that bar, the delta comes back as `{real: false}` and the value is `null` — never a number, never zero.
**A window with fewer than two runs on either side is never a change.** One run against one run has no dispersion to measure: an answer that named you yesterday and did not today is a single answer changing its mind, not a hundred-point move. Both windows need at least two runs before a delta can be `real: true` at all.
`real: false` means one thing precisely: **no change we can prove.** It is not a claim that nothing moved. It is a refusal to present movement as a finding when the sample cannot distinguish it from noise.
**At n = 1, most deltas will not clear the gate.** With one run per prompt per model per day, a seven-day window holds seven observations per cell, and a two-proportion test on seven-versus-seven observations only detects large swings. Expect many `real: false` results on Starter. That is the method working, not the product failing to measure.
Three ways to get more deltas that clear the gate, in order of effect:
Aggregating fourteen or thirty days instead of seven multiplies the runs behind each rate without changing anything about your setup.
Read a rate at the topic or model level rather than for one prompt on one model. Coarser cells pool more runs.
Growth, Business and Agency sample three runs where Starter samples one, tripling the observations per day for the same prompts.
## What this method cannot tell you
Attensira asks your prompts on a schedule. It does not see the questions real people asked, or how many of them there were. It measures how the models answer, not how often anyone asks.
Coverage is limited to the channels listed above, in the countries you configure. A surface you do not track is not measured, and never reported as a zero. A configured country is a local reading only on the consumer surfaces; on Claude and the API fallbacks it is a location stated in the question.
The same prompt can produce different answers minutes apart with nothing having changed. This is exactly why sampling depth and the significance gate exist, and why a single run is an observation rather than a fact about the model.
Page fetches by AI crawlers are a separate dataset with separate mechanics. See [AI traffic](/traffic/ai-traffic).
Data retention follows your plan: 3 months on Starter, 12 on Growth, unlimited on Business. Handling of the data itself is covered by the [privacy policy](https://attensira.com/privacy) and [terms of service](https://attensira.com/tos).
# Page changes
Source: https://docs.attensira.com/measure/page-changes
How Attensira sees a change on one of your own pages, matches it to what we suggested or shipped, and measures the weeks before and after it, without ever saying the change caused anything.
Every read of one of your own pages is compared with the version we hold of it. When a read finds the page different, Attensira keeps the new version, records what changed, checks it against the suggestions and deliveries it has on file for that page, and books the same [before-and-after re-reads](/measure/before-and-after) a merged pull request gets. That covers a change we shipped, a change you made from our advice without telling us, and a change nobody suggested.
All of it is observation. Seeing a change never marks a work item shipped, approves anything or changes its state. A person confirms that, if they want it recorded.
## How often a page is read
The pages in your site's [inventory](/traffic/pages#how-does-attensira-know-which-pages-you-have) are read on a schedule:
* **Weekly.** A page is read again 7 days after its last read.
* **Daily while a suggestion is open.** A page that one of your open suggestions targets is read about once a day, until 30 days after the suggestion was filed. Open means the suggestion has not been declined. At most **25 pages** per workspace are on the daily read at once; when a new one would be the 26th, the watch that ends soonest goes back to weekly.
A daily read is an ordinary page read and is metered like one. See [Credits](/account/credits).
When the agent reads a page live during a chat or a run, that read is kept as a version too.
A page is read as the HTML your server returns. JavaScript is not run, so a title or a paragraph that only appears after scripts run is not seen, and a change to it is not seen either.
## What a version records
Each version keeps the fields a person edits on purpose:
| Field | What is kept |
| - | - |
| Title | The `` text, up to 300 characters |
| Meta description | The description, in either attribute order, up to 300 characters |
| Main headings and subheadings | The H1s and H2s, in page order, from the first 25 headings on the page |
| Canonical URL, robots directive | The canonical link and the robots meta tag |
| Social title and description | The Open Graph title and description |
| Structured data | The JSON-LD `@type` values the page declares |
| FAQ and list structure | How many FAQ questions it answers (from FAQ schema, then `` blocks, then H2/H3 headings ending in "?"), and how many lists, list items and tables it has |
| Body text | The first 1,500 words of visible text, a fingerprint of all of it, and its full word count |
| Status | The HTTP status the page answered with |
A page that answers with an error (a 404, say) is recorded too: the version is its status. A page we could not reach at all is not recorded, because there was no answer to keep.
### Not captured is not absent
Versions taken before Attensira recorded a field cannot say what that field was. In a comparison, such a field is shown as **not captured**, never as removed, added or changed. The same goes for every field of a read that was not a 200: an error page has a status and nothing else to compare.
## What counts as a change
A read is compared with the **current version** of the page, not with the last read. A new version is recorded when:
* any of the fields above other than the body differs (title, description, H1, H2, canonical, robots, social title and description, structured data types, structure);
* the status differs, for example 200 to 404; or
* the body text differs from the current version by more than noise: more than 3 bits of 64 in a similarity fingerprint taken over the title, the description and the first 1,500 words.
What does not count:
* **Rotating tokens.** Words containing a digit (dates, counters, "12 people viewing", prices in a banner) and long hexadecimal strings (session ids, build hashes) are left out of the body comparison. A price that changes in the body text alone is therefore not a change; one that changes in the title is.
* **Small body drift.** A body edit too small to clear the threshold is the same version, seen again. Because every read is compared with the current version, several small edits that add up past the threshold are recorded as a change on the read that crosses it.
* **Reordering.** H1s, H2s and structured data types are compared as sets, so moving a subheading up the page is not a change.
* **A better reader.** When Attensira starts recording more about a page, the first read with the new reader is stored as a **recapture**. It is never treated as a change, and comparisons start from it.
### Reverts
A change whose fields are the same as an earlier version's, from the last 180 days, is a **revert**: the page went back. A page that returns to exactly the bytes it served before is matched to that earlier version too, so a revert is never lost.
A revert is listed as its own page change. When it goes back on a change that is still being measured, that change's outstanding re-reads are cancelled and the change is marked **reverted**, with the dates: "the change was reverted on the page between 2 Sep 2026 and 9 Sep 2026". There is nothing left on the page to measure.
## When a change happened: the seen window
A read only tells us what the page says now. So a change is dated by two reads:
* **After:** the last read that still saw the old version.
* **By:** the first read that saw the new one.
The edit happened somewhere in between. On the weekly read that window is about 7 days, longer when a read runs late; on the daily read it is usually one. Every sentence about a change is dated from these two reads: "first seen live on 9 Sep 2026", or "changed between 2 Sep 2026 and 9 Sep 2026". When the window is wider than **10 days**, the card says so: "We read this page rarely: the change happened somewhere in a window wider than 10 days."
## How a change is matched to a suggestion
A change is compared with the work items in this workspace that target the same page (its URL with or without `www` or a trailing slash). Each suggestion names a field, such as the title or the meta description; a pull request may move any field, since the file it changes can render any part of the page.
A suggestion filed after the read that first saw the change cannot explain it: the page was already that way.
| Match | When | What the card says |
| - | - | - |
| **Shipped by us** | A pull request, CMS draft or rewrite of ours went live no later than the read that saw the change, and the field it targets changed | "Our pull request for this page's title: merged 3 Sep 2026, first seen live 9 Sep 2026." |
| **Applied suggestion** | Nothing of ours was delivered, the suggestion came first, and the words we suggested are in that field on the live page | "We suggested this title on 3 Sep 2026. It was first seen live on 9 Sep 2026." |
| **Close to suggestion** | The field changed to words close to ours: of all the words in either, at least 60% are in both (title, description and main heading only) | "We suggested a new title on 3 Sep 2026. A similar title was first seen live on 9 Sep 2026." |
| **Field flagged** | The field we suggested changing changed, to other words | "We flagged this page's title on 3 Sep 2026. It changed on the page between 2 Sep 2026 and 9 Sep 2026." |
| **Draft published** | A page we had never read appeared, and its title or main heading is an article we drafted in the last 90 days (or it is the URL you gave when you marked the article shipped) | "The article we drafted on 3 Sep 2026 was first seen live at [https://example.com/blog/post](https://example.com/blog/post) on 9 Sep 2026." |
| **Unattributed** | Nothing on file matches | "This page's title changed between 2 Sep 2026 and 9 Sep 2026. We had not suggested it." |
| **Revert** | The page went back to an earlier version | "The title was changed back to the earlier version between 2 Sep 2026 and 9 Sep 2026." |
The strength of a match is shown with it, in three words: **exact** (our words, or our delivery, are on the page), **close**, and **field** (the field moved, to other words). Unattributed changes and reverts have none.
* **Exact** for a pull request or an article means the field's new words, at least three of them, are in the file we wrote. A file is never on the page word for word.
* **Timing.** A suggestion filed inside the seen window may have come after the edit. Only an exact match is kept then, and the card adds: "We cannot tell whether this happened before or after our suggestion."
* **Declined suggestions still match.** If you declined a suggestion and then made the change, it is still advice followed, and the card says when you declined it.
* **Several matches.** The strongest match wins, then a delivery of ours over advice, then the most recent suggestion. The others are kept as also matched.
* **New pages.** A page we had never read is filed only when it matches an article we drafted. A new page nothing explains is not a change.
* **Advice that was never filed.** A suggestion made only in a chat, never filed as a work item, cannot be matched.
## The before and after
A change is measured on tracked prompts, picked in this order:
1. the matched suggestion's own prompts;
2. else the prompts of every suggestion that targets the page;
3. else the prompts whose answers cited the page in the 28 days before the last read that still saw the old version, most cited first.
At most **25 prompts**. When none apply, nothing is measured and the card says "no tracked prompt targets or cites this page".
The re-reads are the usual three, at **+7, +14 and +30 days** from the read that first saw the change. Each compares two windows of the prompts' daily reading slots:
* **Before:** the 7 slot days ending the day before the last read that still saw the old version. The days of the seen window, when the change may or may not have been live, belong to neither side.
* **After:** the 7 slot days centred on the +N date.
A re-read booked this way does not check the page for our words again: the read that saw the change is the evidence that it is live. A change that is reverted before its re-reads run has them cancelled.
**Nothing is measured twice.** When the matched suggestion already has re-reads from a merge or from being marked shipped, the page change uses those. When a change matched its suggestion exactly or closely (or was our delivery) and the crawl booked the re-reads first, marking the suggestion shipped later books nothing new. A **field flagged** change is measured as a page change only, not as the suggestion's result: the words on the page are not the ones we suggested.
### One line for the page, decided once
Each re-read has its own verdict per prompt and engine, exactly as on [Before and after](/measure/before-and-after). When the +30 re-read resolves, its answers are summed across every prompt and engine into one before and one after, the same significance test is run once on the sums, and the result is stored as the change's **pooled** line. It is never recomputed when you look at it again.
### Sample-size floors
| Shown | Only when |
| - | - |
| The pooled line as two rates | At least **30** successful answers in each window, and a before window read on at least **5** slot days |
| One engine's row | At least **10** successful answers in each window |
Below a floor the card shows the counts and no percentage: "Too early to say (n=108 before, n=112 after)."
### The labels
Every change carries one of three labels:
| Label | Means |
| - | - |
| **moved** | The pooled line cleared the significance test, above the floors |
| **too early** | The +30 re-read has not happened yet, the floors are not met, or there was too little before the change to compare against |
| **nothing we can prove yet** | The windows do not differ past the test, or the change was reverted, or there was nothing to measure |
Only **moved** gets the measurement sentence, and it puts the two rates side by side:
> In the 7 days around 16 Sep 2026, this page's 4 prompts were named in 31% of answers (n=112), against 12% (n=108) in the 7 days before it changed.
## Why we never say "because"
A re-read shows that the numbers differ between two windows. It cannot rule out everything else that changed in the same weeks: a model update, a competitor's new page, another edit on your site. So the change and the measurement are always two separate sentences, and no sentence about a page change uses "because", "after we shipped", "since we", "thanks to", "led to", "drove", "resulted in" or "as a result". Every template is tested against that list.
If two changes on one page fall in the same weeks, each is measured on its own, over the same answers. Neither can be separated from the other.
## Citations and AI-crawler fetches
The card also shows two descriptive counts. Neither carries a verdict.
* **Citations:** how often answers to the change's prompts cited the page in the re-read's before and after windows, beside how many answers each window had.
* **AI-crawler fetches:** how often AI crawlers fetched the page in the days before the change and the same number of days since it was first seen (up to 7), with a split by provider since first seen. These need the [AI traffic](/traffic/ai-traffic) snippet. Without it the card says fetches are not counted. When the before window is older than the AI-traffic history held for your site, the before count is left out rather than shown as 0.
## Where you see page changes
* **Pages → Changes.** Open a page in [Pages](/traffic/pages) and choose the **Changes** tab: every version we hold, when each change was first seen and what moved, reverts marked, and a field-by-field comparison of any two versions you pick.
* **Work.** One row per workspace, **Page changes on your site**, raised when a change is matched to something of ours and when a change's pooled line is decided. Later events update the same row rather than adding rows. A change nobody suggested, or a revert, does not raise it when it is first seen. The evidence card also appears under the suggestion it matched.
* **Mark shipped from the row.** When our suggested words are on the page from a copy handoff nobody has marked shipped, the row offers **Mark shipped**, which opens the item where you confirm it. See [Mark shipped](/agent/inbox#mark-shipped).
* **The morning brief.** Up to **three** lines under "Changes we saw on your pages:", moved first, for changes first seen or decided in the brief's window (the day before; the week before on Monday). See [Morning brief](/agent/morning-brief).
* **Client reports.** Up to **ten** changes matched to our work in the period, worded as the card words them. Unattributed changes stay on the card. See [Client reports](/agencies/client-reports).
The dashboard reads these from `GET /v2/projects/{id}/pages/versions?url=`, `GET /v2/projects/{id}/pages/diff?url=&from=&to=`, `GET /v2/projects/{id}/page-changes` (filters `state` = `open`, `measured` or `reverted`, `attribution`, `cursor`, `limit` up to 100) and `GET /v2/projects/{id}/page-changes/{change_id}`. These answer a signed-in session only; page changes are not on the REST API or the MCP server yet.
## The agent's page\_changes tool
The agent has a free, read-only tool, `page_changes`, that compares two versions of one of your own pages field by field. With only `url` it compares the current version with the one it replaced; `from` and `to` take any two version ids from the list it returns, in either order. A field one version never read comes back as `not_captured`, with the reason, and the agent is told to say so rather than guess. The URL must be on your workspace's own domain.
It sits in the agent's **Your own data** tool group next to [`page_history`](/agent/deep-read#page_history), which lists a page's versions one after another.
# Prompt state
Source: https://docs.attensira.com/measure/prompt-state
Where you stand on a prompt, in one word per engine and one word overall: who had the answer, read from the latest successful answers, with a dash where nothing was measured.
A rate tells you how often you were named. It does not tell you who had the answer when you were not, and "named in 0% of answers" reads the same whether a rival took every answer or nobody was named at all. Those are different jobs. **State** is the word for that difference: one per engine (the **cell**), and one for the prompt across its engines.
State is folded from the same answers as the rates beside it, so a cell and the percentage next to it never come from two different samples.
## Cell state: one prompt on one engine
A cell is read from the **latest reading in the window that has at least one successful answer** on that engine. Failed answers do not count; a reading where every answer failed is skipped and the one before it is used. With `n` successful answers in that reading:
| State | Shown as | When |
| - | - | - |
| **Named and linked** | ● | You are named in at least half of the answers, **and** your domain is cited in at least half of them |
| **Named** | ◐ | You are named in at least half of the answers |
| **Lost** | ✕ | You are not, and one tracked competitor is named in at least half of them, in the answer's own words |
| **Absent** | ○ | Otherwise: nobody you track is named often enough to hold the answer |
| **Not measured** | — | No successful answer on this engine in the window |
The rules are checked in that order, and the first that holds wins.
* **At least half, not at least one.** A cell is a label on `n` answers. At sampling depth 3, a label that one answer in three could flip would move inside the sampling noise. Ties go to you: named in 1 of 2 answers is **Named**.
* **Lost needs a rival in the words.** A competitor counts only when the answer names it in its text. A competitor that appears only among the links is a source, not a name the answer chose. A lost cell says which rival, the one named in the most answers (ties broken alphabetically), and when the reading was taken.
* **Absent is a measurement.** It means the answers were read and nobody you track was named in enough of them. **Not measured** means there was nothing to read. The grid draws them differently, and never one in place of the other.
A cell that is not measured always says why, on hover:
| Reason | Means |
| - | - |
| not read in this window | The prompt is tracked on this engine and has no reading in the window |
| every read in this window failed | Readings exist and every answer failed. The commonest reason we can show you follows it |
| no answers came back in this window | Readings exist with no answers in them at all |
| this prompt is not read on this engine | Another prompt is read on this engine; this one is not |
## Prompt state: one prompt across its engines
The prompt's state is folded from its cells. Only measured cells count: let **M** be the number of engines with a measured cell.
| State | When |
| - | - |
| **Won** | Named (or named and linked) on at least half of the M engines, rounded up, **and** named and linked on at least one |
| **Contested** | Named on at least one engine, but not enough to have won it |
| **Lost** | Not named on any engine, and lost on at least one |
| **Absent** | Measured on at least one engine, and every measured engine is absent: you are named on none and lost on none |
| **Not measured** | M is 0: no engine has a successful reading in the window. Shown as — |
The precedence is won, then contested, then lost, then absent. A prompt named on one engine and lost on two is **Contested**, not lost: you are in the answer somewhere.
An engine that is not measured is left out of M. It neither helps nor hurts the prompt, because counting it would turn a gap in our coverage into a claim about your brand. A prompt read on four engines, where one failed for the whole window, is judged on the other three.
The board also counts the engines a prompt is **lost on**. That number sorts the losses first, and it is what the analytics overview's "Where are we losing" block ranks by.
## Where you see it
* **Prompts**: the **Standing** column is the prompt state, and each engine column is that engine's cell. You can filter and group by standing; grouped, the groups run worst first (lost, absent, contested, won), with not measured last because "never read" is not a low score. A group's summary counts its prompts in each state, for example "3 lost · 1 contested · 2 won".
* **The prompt page**: each engine shows its state with the `n` it was read over and when, from the same rule, so the page and the board never disagree.
* **CSV export**: a `state` and `lost_on` column per prompt, and a `_state` and `_state_n` column per engine. An empty cell is not measured, which is not absent.
* **Over MCP**: `query_evidence` counts prompts by state (`prompt_state_count`) and filters any metric by `state`. See [MCP tools](/mcp/tools).
## Reading it
State is a word, not a trend. It describes the latest successful answers in the window, so it moves as soon as one reading moves, and a single reading at sampling depth 1 can only ever be all or nothing. Read the `n` beside a cell before you act on it, and use the rates and their [significance test](/measure/reading-the-numbers#why-does-a-change-say-real) for whether anything changed.
Next: [Share of voice](/measure/share-of-voice) for the rate behind **Named**, and [Competitors](/tracking/competitors) for the rivals a **Lost** cell names.
# Reading the numbers
Source: https://docs.attensira.com/measure/reading-the-numbers
How to tell a measured zero from an unmeasured cell, why a rate carries a sample count, and when a change is real.
Every number Attensira reports carries the evidence behind it. A rate arrives with the number of observations it was built from, a change arrives with whether it cleared a significance test, and anything we never measured comes back as `null` rather than as a zero. This page explains how to read all three, in the dashboard and over [MCP](/mcp/overview).
The three rules, in the words the dashboard uses:
* **`0` and `—` are different facts.** A dash means not measured. A 0 means we read the answers and the count was zero.
* **Every rate carries its n.** A percentage never appears without the number of answers behind it.
* **"Nothing we can prove yet"** is what a change says when the sample cannot tell it apart from noise.
## Why is a rate two values instead of one?
A rate is `{value, n}`. `value` is the rate itself, between 0 and 1. `n` is how many successful runs it was built from.
```json theme={null}
{ "value": 0.31, "n": 84 }
```
Read `n` first. A rate of 0.33 built from three runs and a rate of 0.33 built from three hundred are the same number describing very different amounts of evidence. The first will move dramatically on the next reading; the second will not. Small samples move in large steps, and a jump from 0 to 0.33 at `n` of 3 is one answer changing its mind, not a trend.
## Where is n in the dashboard?
Beside the rate. The analytics screens print the count with every rate they show:
| Where | How the rate is written |
| - | - |
| Prompts grid, each rate cell | The percentage, with the count under it: `25%` over `1 of 4 answers`. Below 20 answers the count ends in `· thin`. |
| Prompts grid, share of voice | The same, counted in brand mentions rather than answers: `40%` over `2 of 5 mentions`. |
| Prompts grid, `n` column | The answers read for that prompt in the window. |
| Prompts grid, group headers | `n = 12 prompts`, then each rate as `31% of 214 answers`, or `not read` when nothing in the group was read. |
| Overview headline | The percentage from 20 answers up, and below 20 the count instead: `3 of 12`. Either way `n=… answers` sits beside it. |
Below 20 answers a percentage claims a precision the sample does not have. "0 of 4" says everything "0%" says, and carries its own evidence, which is why the Overview switches to the count and the grid marks the count thin. The 20-answer line is a display rule, not a statistical test; the significance test for changes is described below.
## What does null mean?
`null` means **not measured**. It never means zero.
| What you see | What it means |
| - | - |
| `{"value": 0.24, "n": 96}` | Measured. You were named in 24% of runs. |
| `{"value": 0, "n": 96}` | Measured. The models were asked 96 times and never named you. |
| `{"value": null, "n": 0}` | Not measured. Nothing was read in this window. |
The difference matters because the two failures have opposite remedies. A measured zero is a product problem — the models know your category and do not know you. An unmeasured cell is a configuration or coverage problem, and the number tells you nothing about your visibility at all.
Never render an unmeasured cell as 0%. Reporting `null` as zero invents a failure that was never observed. The dashboard shows an em dash for these, and the API returns `null` so your own tooling can make the same distinction.
## How does the dashboard show a zero and a dash?
On every screen, an unmeasured value is an em dash (—) and a measured zero is a `0` or `0%`. On the Overview, hover a dash, or the n beside the headline figure, and the dashboard says:
> A dash means not measured: no answer was read for it. A 0 means we read the answers and the count was zero.
In the prompts grid a dash is never bare. Each one carries the reason it is a dash, on hover and for screen readers. The reason tells you which gap you are looking at, because the remedies differ:
| Reason on the dash | What it means |
| - | - |
| not read in this window | The prompt is tracked on this model, and no reading was taken in the window you are looking at. |
| every read in this window failed | Readings were attempted and every one failed. The commonest failure reason follows it when there is one. |
| no answers came back in this window | Readings exist but carry no answers at all. |
| this prompt is not read on this engine | Other prompts are read on this model; this one is not. |
| Intent is not classified yet. | The prompt has no intent label yet. This is a missing attribute, not a measurement. |
The prompts grid applies the same rule to standing: `○ Absent` means we read the answers and nobody you track was named, and `— Not measured` means there was nothing to read. See [where you stand on a prompt](/tracking/prompts#where-do-you-stand-on-a-prompt).
The CSV exports from the prompts, pages, sources and competitors grids keep the distinction too. Each file states, in its header, that an empty value is not measured and a 0 is a measured zero. Every rate column has an `_n` column beside it, so you can tell which of the two an empty cell is.
## Why does a change say "real"?
A change is `{value, real}`.
```json theme={null}
{ "value": 0.06, "real": true }
```
`value` is the movement against the immediately preceding window of equal length. `real` says whether that movement cleared a two-proportion z-test at 95% confidence.
When `real` is `false`, `value` is `null`. That is deliberate. Models are not deterministic, and a rate built from a modest sample wanders on its own without anything changing in the world. Rather than draw a line through that noise, Attensira reports no proven change.
So `{"value": null, "real": false}` means **"nothing we can prove yet"**, not "no movement" and not "zero". It is the honest answer to a question the sample cannot yet settle.
## Where does "nothing we can prove yet" appear?
Wherever a change would otherwise be stated:
* **The Overview's trend sentence.** It always includes n, and ends either "a real change" or "nothing we can prove yet". For example: "Named in 31% of answers, up from 26% (n=1,840), a real change." or "Named in 31% of answers (n=214), nothing we can prove yet." A change that clears the significance test is still "nothing we can prove yet" when fewer than 70% of the window's days were read, and the sentence says how many were, for example "read on 4 of 28 days". Two thin samples weeks apart are not a trend. When nothing was read at all, it says "No answers were read in this window yet (n=0), so there is nothing we can prove yet."
* **The prompts grid's Trend column.** A prompt's movement is shown in points (`+6 pts`) only when it cleared the test. Otherwise the cell reads `not proven`, and its hover says: "No change this sample can prove against the window before — nothing we can prove yet."
* **Re-reads after a change ships.** Where a screen sums up a change's re-reads in one word, such as the Overview's "What did we do, and did it work?" block, the **No change** and **Too close to call** verdicts both read "Nothing we can prove yet": two different findings, the same honest sentence. On the Overview, a change with too few answers from before it to compare against reads the same, and so does one that moved up on some prompts and down on as many others. See [Before and after](/measure/before-and-after) for the full verdicts.
None of these say why a number moved. A real change means the number moved by more than the noise. It does not mean a particular piece of work moved it.
At the Starter sampling depth of one run per prompt per model per day, most movements will not clear the gate. That is the test working, not the product failing. [How we collect data](/measure/how-we-collect-data) explains sampling depth, and [Plans](/account/plans) shows which depth each plan runs at.
## Why was a finding held as noise?
The hourly [detection](/agent/findings) pass applies the same idea to what it files in your Inbox. A rule that fires on three quiet days is not yet proof that anything changed, so before a finding is filed its last three days are compared with the seven before them, on successful reads only.
* **Significant at p \< 0.01** on Fisher's exact test: it is filed. That bar is stricter than the 95% test above, because a filed finding asks for your attention.
* **Not significant:** a [decision](/agent/decisions#noise-or-a-real-change) reads the same counts, day by day, and judges whether the change is real or noise. It reads numbers only.
* **Held as noise:** the decision was confident the change is not real. The finding is not filed and starts no run, and it is checked again an hour later on fresh readings.
Each hold is recorded with its `n`: the successful reads, across both windows, it was judged on. As everywhere else, `n` is the first thing to read. A held finding at an `n` of 12 is a handful of answers that wandered. A dip of the same size at an `n` of 300 is far more likely to clear the test, and a change that clears it is filed without a question.
## What do tracked and readable mean?
Per-model metrics carry two booleans that both look like a failure and are not.
**`tracked: false`** — this model is not configured for this workspace, so it was never asked. There is no visibility number because no question was put to it. Reporting it as 0% would invent a failure. Add the model in [Models](/tracking/models) if you want it measured.
**`readable: false`** — no reader exists for that surface, so it cannot produce a reading at all. This is a property of the model, not of your workspace or your plan, and adding the model will not change it.
Both are different again from `{"value": 0, "n": 40}`, which means the model *was* asked forty times and did not name you. Only the last of the three is a result.
## What is the unit behind every rate?
A **run**: one prompt, sent to one model, for one country, at one moment. Not a reading, not an answer, not an occurrence of your name.
Every rate on the site flattens all the runs in the window into a single pool and divides. A prompt read for thirty days at a sampling depth of three contributes ninety runs to that pool, weighted the same as any other ninety. Runs that failed are stored for debugging but excluded from `n`, so a model outage shrinks your sample rather than depressing your rate.
## A dash, a zero, and an empty list
The same rule holds for everything that is not a rate:
| What you see | What it means |
| - | - |
| **—** (`null` on the wire) | Not measured, or not known. Nothing was read, or the reading that would answer it has not happened yet |
| **0** | Measured, and the count or rate was zero |
| An empty list (`[]`) | Looked, and found none |
A dash never stands alone. Wherever the product shows one, it says why on hover or in place: "not read in this window", "not scored yet", "recorded before we kept the full brand order". A dash without a reason asks you to guess which of several very different gaps it was.
Lists follow the same split. An answer's [brand order](/measure/share-of-voice#the-full-brand-order) is `null` until the answer has been read for it and `[]` once it has been read and named no brand. A link's [context](/measure/citations-and-sources#what-was-each-link-cited-for) is `null` when we never looked and `""` when we looked and the answer's text does not mention the link. A [stance](/measure/sentiment) is `unscored` when nobody has read the answer for it and `none` when it was read and takes no position. In each pair, only the second is a reading.
## Gaps
A table that is missing rows is not the same as a table whose rows are zero. So when `query_evidence` answers a question it cannot fully measure, it lists every hole as a **gap**: a `key` saying where the hole is, and a `reason` saying why.
```json theme={null}
"gaps": [
{ "key": "2026-08-01..2026-08-09", "reason": "before_first_reading" },
{ "key": "engine=gemini", "reason": "no_readings" }
]
```
The key is a source (`search_console`), a run of days (`2026-08-01..2026-08-09`), one day (`day:2026-08-04`), or one row's key (`engine=gemini`). Consecutive missing days are one gap, not one per day. The reasons include days before your first tracked prompt, a source that is not connected, days not recorded yet, a value not read yet, and a day that was cut short. An empty `gaps` list is the claim that nothing is missing.
Read the gaps before you read the rows. A 0 next to a gap is not a measurement of that gap.
## Truncated days and truncated tables
Two different things can be cut short, and both are said.
* **A truncated day.** Where a source keeps only the top rows of a day, as Search Console's detail by search query does, that day's numbers for a single query are a floor, not a total. The day carries a `truncated_day` gap. A page's own totals are not affected: they are stored whole, not added up from the queries.
* **A truncated table.** When an answer has more rows than its `limit`, `truncated` is `true` and the rows are the strongest, or the newest days or weeks when grouped by time. A count shown from a truncated read is "at least N", never N.
## Reading these over MCP
The shapes are identical through the [MCP server](/mcp/overview) — `get_analytics` returns the same `{value, n}` and `{value, real}` objects, and the same `tracked` and `readable` flags per model.
If you are building on top of the API, carry the distinction through rather than flattening it. Coercing `null` to `0` at the edge of your system is the single most common way an accurate measurement becomes a wrong report.
## What do the verdicts on a shipped change mean?
The [analytics overview](/measure/analytics-overview) and a prompt's changes timeline judge each shipped change in one of four words: **Moved up**, **Moved down**, **Nothing we can prove yet** and **Too early**. None of them says the change caused the movement. [The verdict words](/measure/analytics-overview#the-verdict-words) gives each one's meaning, the text shown on hover, and how a change's re-reads decide which word it gets.
# Sentiment
Source: https://docs.attensira.com/measure/sentiment
How answers talk about your brand where they take a position at all. An answer that names you without judging you is left out of the score, never counted as neutral.
Sentiment answers one question: when an answer has an opinion about your brand, is it a good one? It does not answer "how do answers feel about us on average", because most answers that name a brand hold no opinion about it, and scoring those would bury every real opinion under a pile of price quotes and feature lists.
## The one rule: no stance is not neutral
Every successful answer is read for its **stance** on your brand. The stance is one of three things, or nothing:
| Stance | Means | In the score? |
| - | - | - |
| **Positive**, **Neutral** or **Negative** | The answer takes a position on your brand: praise, criticism, a recommendation, a reservation, a balanced verdict | Yes |
| **No clear stance** | The answer was read and judges nothing: a price, a feature, a bare entry in a list | No |
| **Not scored yet** | Nobody has read the answer for its stance yet | No |
**Neutral** and **No clear stance** look alike and mean opposite things. "Ledgerly is one of several reasonable options" is a neutral verdict and counts. "Ledgerly starts at \$12 per seat" names you and judges nothing; it is left out. Counting it as neutral would move your score whenever the mix of questions you track moved, which is not sentiment.
**Not scored yet** is different again: it is the absence of a reading, not a reading. A new answer is not scored yet until it has been read for its stance, which happens after it is stored, never while it is being sampled.
## How it is calculated
Every number on the Sentiment page divides by **scored answers**: the answers with a stance. Never by answers read.
```text theme={null}
positive share = answers with a positive stance
÷ answers with any stance (scored)
```
The neutral and negative shares are the same over the same denominator, so the three sum to 100% of the scored answers.
The **sentiment score** is the average stance of the scored answers on a scale from **-1 to +1**. Above +0.15 reads as mostly positive; below -0.15, mostly negative. A score of 0 is a real reading: the opinions cancelled out. A score of `null`, shown as —, means no answer in the window took a position, and there is nothing to average.
The page shows both numbers beside each other, with their counts: the scored answers, the answers read, and the answers not scored yet. When the scored count is small, read it before the score. Five scored answers out of three hundred read is five opinions, not a verdict on your brand.
## The Sentiment page
**Analytics → Sentiment** shows, for the window:
* the score and the positive, neutral and negative shares, with the scored count;
* the positive share's change against the previous window of the same length, only when it clears the [significance test](/measure/reading-the-numbers#why-does-a-change-say-real);
* the score by day and the positive share by day, as two separate charts. A day with no scored answer is a gap in the line, not a zero;
* one row per answer engine and per topic, each dividing by its **own** scored count;
* the scored answers themselves, each with the sentence that carries the stance, so you can check the score against the words.
Filtering by engine or topic recomputes every number for that cut. Filtering by stance only narrows the list of answers: the shares stay what they are, because a share filtered down to itself would always read 100%.
### Flagging a score
If a stance looks wrong, flag the answer. A flag is a note that somebody on the workspace disagrees with the score. It is an annotation, never an edit: it changes no number on the page.
## Per-brand stance in the brand order
Each answer's [brand order](/measure/share-of-voice#the-full-brand-order) lists every brand it names. Each brand in it carries its own stance under the same rule: a brand the answer names without judging has no stance, not a neutral one.
On the prompt page, your own entry in the brand order shows the answer's stance on you: Positive, Neutral, Negative, or **No clear stance**. A competitor's entry shows a dash with "not scored yet": the Sentiment page and the prompt page do not score competitors.
Over MCP and the REST API, `answer_history` and `answer_slice` rows carry the stance on you as `stance` (`positive`, `neutral`, `negative`, `none` or `unscored`), and the brand order as `brands`, where each brand has its own `stance_label` and `stance_score`. A brand with no stance has no `stance_label` and a `stance_score` of `null`. A `stance_score` of `0` is a balanced verdict.
## Filtering answers by sentiment
`answer_slice` takes a `sentiment` term over your brand's stance. It keeps the same distinction:
* `positive`, `neutral` or `negative`: answers with that stance;
* `none`: answers that were scored and took no stance;
* `unscored`: answers nobody has scored yet.
`none` and `unscored` are never folded into `neutral`, or into each other. See [MCP tools](/mcp/tools#answer_slice).
## What it covers
Stance is read for answers from the last 90 days. An answer older than that, read before stance scoring existed, stays not scored yet and is not in any score.
The stance is read from the answer's text by a model. It can be wrong, and that is why every scored answer is listed with its sentence and can be flagged. Like every other reading, the text is stored verbatim, so when the scoring improves it is re-run over the stored answers in that window rather than starting a new series.
# Share of voice
Source: https://docs.attensira.com/measure/share-of-voice
How often a model names your brand in its answer, measured as successful runs naming you divided by all successful runs in the window.
Share of voice is the fraction of successful runs in which a model named your brand in its answer text. It is the count of successful runs where your brand was named, divided by every successful run in the window. Nothing else enters the calculation: not link counts, not answer length, not position.
Share of voice here is a **rate, not a slice of a pie**. It answers "in what fraction of answers were you named?" — never "what fraction of all brand mentions were yours?" It does not sum to 100% across brands. Most tools in this category use the name for the pie version; we do not, for the reason in [Why ours does not sum to 100%](#why-ours-does-not-sum-to-100) below.
## What counts?
A mention is your brand name appearing in the answer the model wrote. No link is required. A model can describe you at length, recommend you, and never attach a URL — that still counts. A link to your domain is a separate thing entirely, counted as a [citation](/measure/citation-rate).
The unit of measurement is a **run**: one prompt, sent to one model, for one country, at one point in time. Not a reading, not an answer paragraph, not a keyword occurrence. If your name appears eleven times in one answer, that run contributes exactly one mention, the same as an answer that names you once.
## How is it calculated?
```text theme={null}
share of voice = successful runs naming your brand
÷ all successful runs in the window
```
Failed runs are stored for debugging but excluded from the denominator, so a model outage lowers your sample size rather than your rate. See [How we collect data](/measure/how-we-collect-data) for what makes a run succeed.
## Does sampling more often change my number?
No, and this is deliberate. Reading the same prompt three times instead of once triples the runs behind the number — and roughly triples the URLs those answers carried — but both the numerator and the denominator grow together, so the rate holds and only `n` grows. Sampling deeper buys you confidence, not a different number.
The same property is what makes windows comparable. A window read at greater depth than the one before it is not "up"; the [significance gate](/measure/how-we-collect-data) compares two rates using both sample sizes, so a change has to clear the noise floor for the `n` actually involved.
Two things do scale with sampling depth, and they are counts rather than rates: the number of citations collected, and the number of answers read. Read those as evidence volume, never as movement.
## Why ours does not sum to 100%
Competitor share of voice uses the **same denominator** — all successful runs in the window. That has a consequence worth internalising: competitor rates do not sum to 1. Several brands can be named in one answer, or none can. A window where you sit at 40% and three competitors sit at 55%, 30% and 20% is perfectly ordinary and describes reality accurately.
We could compute the version that does sum to 1 — your mentions divided by every tracked brand's mentions. We deliberately do not, because its denominator would be **the competitor list you typed**. Add a competitor and your headline number falls; remove one and it rises. Nothing about your visibility would have changed. A number that moves when you edit a settings page is not a measurement, and we would rather publish one that only moves when the models do.
This means our share of voice is not comparable to a number under the same name from a tool that computes the pie version. Ours will generally look larger, because it is answering a different question.
## How do I read the number?
Every rate arrives as `{value, n}`. `value` is the rate; `n` is the number of successful runs behind it. A `value` of `null` means the cell was never measured — that is not zero. A `value` of `0` with `n` above zero is a real, measured zero: the model was asked and did not name you.
Read `n` before you read `value`. At a sampling depth of one run per prompt per model per day, a single week gives you seven runs per prompt-model pair, and a rate built on seven runs moves in large steps. [Reading the numbers](/measure/reading-the-numbers) covers small samples, deltas and unmeasured cells in detail.
## Where did you come in the answer?
Being named is the rate. Where you were named is a separate reading, and Attensira keeps two versions of it on purpose.
### Your position: among the names you track
Your **position** is where your brand first appears in an answer, counting only the names you track: you and your tracked competitors. 1 means no tracked name came before yours. It is always shown with its base, as in "#2 of 5 tracked names", where the base is you plus every tracked competitor that answer also named.
This is the position every board, chart and average reports, and it stays that way. It is measured from the answer's text against a list you control, so it is the same reading every time the answer is read. The base is what keeps it honest: "#2" alone reads as second in the answer, and it means second among the names you asked us to look for. Track one more competitor and an answer that named it before you moves you from #1 of 2 to #2 of 3, with nothing about the answer changed.
An answer that did not name you has no position, never position 0, and is left out of the average.
### The full brand order
Each answer also carries its **brand order**: every brand it names, tracked or not, in the order it first names them. It is read from the answer's text after the answer is stored, and the order is fixed by where each name first appears in the text, not by the model that reads it. A name the text does not contain is dropped, and a brand is linked to a domain only when it matches you, a tracked competitor or a site the answer cited.
On the prompt page, every answer you open from the day-by-day strip lists its brand order with each brand's [stance](/measure/sentiment#per-brand-stance-in-the-brand-order) and marks which one is you. Over MCP and the REST API it is the `brands` list on `answer_history` and `answer_slice` rows: each brand with `name`, `domain`, `position`, `tracked`, `is_self` and, for a tracked rival, `competitor_domain`.
Your place in the brand order can be further down than your position, because untracked brands count in it. That is why the brand order is shown per answer, as evidence, and the boards keep your position among tracked names.
A competitor's average place, where a rival tends to come when it is named, is read from the brand order, because there is no tracked-name position for a rival. It is reported by `query_evidence` as `competitor_position`, and it is `null` until at least one answer naming that rival has been read for its brand order.
Answers from the last 90 days have a brand order. An answer older than that, read before the brand order was kept, shows a dash and "recorded before we kept the full brand order", never an empty order: it may well have named five brands. A brand order of `[]` is a reading: the answer named no brand at all.
## What this measure gets wrong
Detecting a mention means finding your brand name in the answer text, as a whole word. That handles the ordinary cases — possessives, hyphenation, your domain inside a URL — and it will not count "architecture" as a mention of a brand called Arc.
It cannot yet tell a brand from its own name used as an ordinary word. If your brand is called Linear, an answer explaining that something "scales in linear time" is counted as a mention. The error runs one way — brands whose names are common words read **high**, never low — and it is worth knowing about when you read your own number. We store every answer verbatim, so when the detection improves we can re-run it over history rather than starting a new series.
If your brand name is a common word, open a few answers on the [prompt detail](/measure/reading-the-numbers) view and read them before you trust the rate.
# How to win one answer
Source: https://docs.attensira.com/measure/win-this-answer
The win plan for a single prompt: per model, what decided the answer, which rung you are on, the one next action, and when we read it again.
[Who decides an answer](/measure/deciding-sources) tells you which pages the models leaned on. The **win plan** turns that into one thing to do — per model, with the evidence under it and the date we will find out whether it worked.
## One action per model, never a list
A plan with five actions per model is a plan nobody works. So each model gets:
* **Up to five deciding sources**, each with its share, its **n**, and whether you are on it already.
* **A rung**: `proven` (you are cited among the pages that decide this answer), `better` (you are cited and a competitor sits above you), or `new` (nothing of yours is there).
* **One next action**: fix a page, get listed, pitch a listing, answer a thread, or write something cite-able that does not exist yet — with the page or domain to work on and the citations it rests on.
* **A credits estimate** for that action. It is an estimate; the meter on the run is what you are actually charged.
The models are never averaged. The move that wins a prompt on ChatGPT is not the move that wins the same prompt on Perplexity, and one blended plan would fit neither.
Where a model was read on two surfaces — a consumer answer and an API answer — the plan takes the surface with the most answers behind it and says which one it took, rather than averaging two different products.
## Where the rung comes from
The same rule the [deciding-sources map](/measure/deciding-sources) follows: the static per-engine table until your own workspace has read at least 20 successful answers on that source, and your own history after that. Every rung says which of the two it came from.
## What the plan will not say
* **No forecast.** No predicted rank, no predicted lift, no percentage that has not been measured.
* **No movement language.** Nothing "improved" or "climbed": until a re-read comes back **moved**, there is no proven change, and the plan ends on that sentence in so many words. Once the workspace has a moved verdict, the footer may use movement wording.
* **No number without its n.** The plan states the evidence rung it is entitled to — `measured` at 7 or more answers, `published` between 2 and 6, `hypothesis` at one.
## When we read it again
Every plan ends with when the answer gets re-read. The date is the day the next booked [re-read](/measure/before-and-after) for that prompt is actually read, a few days after its +7, +14 or +30 day mark. If nothing has booked one yet, it says **"after the first action lands"** rather than naming a date nothing is going to keep.
## Where to find it
* On the **prompt page**, as the first section, **How to win this answer**. See [Prompts](/tracking/prompts#the-prompt-page).
* The [Daily win plan](/agent/autopilot) works from the same plan every morning.
* In **chat**, whenever you ask how to win a prompt — the agent follows the same procedure and shows you the sections per model.
* As the [MCP tool](/mcp/tools) **`win_plan`**. It takes a required `prompt` — a prompt id or the prompt's exact text — and an optional `days` window. Ask for a prompt you are losing; [`deciding_sources`](/measure/deciding-sources) says which those are.
* Over REST it is [`GET /v1/win-plan`](/api/endpoints#get-v1win-plan), with the same payload as `win_plan`.
The plan is composed over the deciding-sources map rather than recomputed from the readings, so the map, the page and the tool can never disagree about which page decided an answer.
# Limitations
Source: https://docs.attensira.com/reference/limitations
What Attensira does not measure or promise, where its methods are approximate, and how to read numbers accordingly.
Every measurement system has edges, and so does every agency. This page is the honest account of ours: what we promise and what we don't, how detection actually works, where noise lives, and what we cannot tell you. Read it before you make a decision on an Attensira number, and read it again before you present one to someone else.
## We promise the work, not the outcome
Your budget commits us to a volume of work each month, and work we don't deliver isn't charged. That is the whole promise. We do not promise a ranking, a citation, a mention, traffic or revenue: no one controls what an assistant or a search engine says, and a change that is never published cannot work. See [Plans](/account/plans#what-a-budget-does-not-buy).
The commitment has edges of its own:
* **Only some work is counted.** Articles, page fixes and outreach pitches are committed and counted. Listing fixes are not counted yet, so nothing is committed for them, and monitoring is not a deliverable. On [Costs](/account/costs#by-kind-of-work) these read **not tracked yet**, which never means zero delivered.
* **Work waiting on you counts as delivered.** A pull request you have not merged, or an article you have not pasted in, is finished on our side. It is never a shortfall, and it cannot move a number until it is live.
* **Some work only you can do.** No person on our side does client work, so where an action needs you (merging a pull request, sending the first pitches from your own mailbox, connecting an integration), it waits in the [Inbox](/agent/inbox) until you do it.
## Not every surface is measured yet
Signup offers every place you might want to be found: the assistants, Google, YouTube, Reddit and review sites. The agents work on all of the ones you choose, but not all of them can be measured for every workspace. A surface with no reader, or one your plan does not include, says **not measured yet** rather than showing a number. See [Models with no reader](#models-with-no-reader-are-unmeasured-not-zero) below.
## How a mention is actually detected
A mention is a **case-insensitive whole-word match** of your brand name, or a **host-aware match** of your domain, against the text of a model's answer. That is the whole method — there is no entity resolution and no disambiguation against a knowledge graph.
For your **name**, boundaries are any non-letter, non-digit character, so possessives ("Acme's"), hyphenation ("Acme-shaped") and punctuation all count as named.
For your **domain**, the rule is tighter, because ordinary word boundaries would hand other people's hostnames to you. A label cannot be extended on either side: `fake-acme.com`, `acme.community` and `acme.com.evil.test` are three different owners and none of them counts as a mention of `acme.com`. Your own subdomains do count — `www.acme.com` and `blog.acme.com` are yours — and so does a domain inside a URL (`acme.com/pricing`) or at the end of a sentence ("we recommend acme.com.").
The boundary rules remove one class of error. A brand called Arc is no longer matched by "architecture", "march" or "search"; a brand called Notion is no longer matched by "notional".
It does not remove the harder one. A brand whose name is also an ordinary word is still counted when that word is used in its ordinary sense: for a brand called Linear, an answer explaining that something "scales in linear time" reads as a mention. Separating those needs a judgement about what the answer is discussing, not a longer search string. **The error runs one way — such a brand reads high, never low.**
Everything built on mentions inherits this. Your share of voice inherits it. Every competitor rate you compare against inherits it. If a competitor's name is a common word and yours is not, the comparison is unfair in their favour, and vice versa.
We store the **verbatim answer text** for every successful run precisely because of this. The stored answer is the evidence: you can read what the model actually said, and detection can be re-run over the same history when the method improves, without losing the past.
If your brand name is a common English word, check a handful of stored answers before trusting your rate. Two minutes of reading tells you whether your number is measuring your brand or the dictionary.
## Models are not deterministic
Ask the same model the same prompt twice and you can get two different answers. Ask it an hour later and you can get a third. This is a property of the models, not a fault in our collection.
Sampling is the response. We put a prompt to a model more than once per reading where your plan allows it — n=1 on Starter, n=3 on Growth, Business and Agency — so that a rate is computed from several draws rather than one. Gating deltas is the second response: a change has to clear a statistical bar before we will call it a change at all.
## Why a small sample shows "no proven change"
Every delta is tested with a two-proportion z-test at 95% significance. If the movement does not clear that bar, you get `{real: false, value: null}` — which means **we cannot prove a change**, and never means the change was zero.
At n=1, this will be the common outcome. One draw per prompt per model per day is a thin sample, so most week-to-week wobble sits under the noise floor and is reported as no proven change. That is the system working: it is refusing to sell you a story that the data does not support.
If you need movements confirmed faster, sampling depth is the lever — n=3 gives three draws per reading. See [Plans](/account/plans).
## null is not zero, and small n is not a rate
Two shapes carry the numbers. A rate is `{value, n}`. A delta is `{value, real}`.
* `value: null` means **not measured**. Nothing was observed, so nothing can be reported.
* `value: 0` with `n > 0` means **measured zero**. Runs happened; none named you.
These are different facts and they must not be collapsed into one chart line. A dashboard that draws null as zero is telling you a model rejected you when in truth it was never asked.
Small `n` deserves the same care. A rate of 33% at n=3 is one hit out of three draws; a single different answer moves it to 0% or 67%. Look at `n` before you quote the percentage, and prefer wider windows over single days.
## Models with no reader are unmeasured, not zero
Per-model metrics carry `tracked` and `readable`.
* `tracked: false` — this model is not configured for your workspace, so it was **never queried**. Rendering that as 0% invents a failure that did not happen.
* `readable: false` — no reader exists for that surface, so we cannot collect it at all.
Not every model surface has an API reader. `google_ai`, in particular, has no API reader — it is read through the consumer surface instead, so answers do arrive, thinner and less regularly than on the API-backed channels. Two other things narrow what you see. A new workspace starts with four model surfaces turned on, not five: Gemini is switched on by hand, so an empty Gemini was most likely never switched on. And `gemini` is gated to Growth, Business and Agency: on Starter it is never queried, whatever the setting says. See [Models](/tracking/models) and [Plans](/account/plans).
## Competitor rates do not sum to 100%
Competitor rates use the **same denominator as yours**: successful runs where that competitor was named, over all successful runs in the window. They are independent rates, not slices of a pie.
One answer can name you and three competitors, so four rates each rise from the same run. Adding them up produces a number that can exceed 100% and means nothing. There is no share-of-voice metric in Attensira that divides a fixed total between brands.
The `share_of_voice` field carries the rate defined above — runs naming the brand over all successful runs. Most tools in this category use that name for the pie version instead, so a number under the same name from another tool is not comparable to this one.
## Citation position is not a rank of your brand
`AvgPosition` is the mean index of a URL within the model's own citation list, computed **per source domain**. It answers "when this domain is cited, how far down the list does it sit". It is not a leaderboard position, not a ranking of your brand against competitors, and not comparable to a search engine result position.
There is also no visibility score in Attensira. "Visibility" appears as a label on a section of the product and as a value in the `get_analytics` area enum; it is not a computed metric.
Your brand's own position is a different number: where you first appear in an answer, counted only among the names you track, and shown with that base ("#2 of 5 tracked names"). Track one more competitor and it can move without the answer changing. See [Share of voice](/measure/share-of-voice#where-did-you-come-in-the-answer).
## Sentiment rests on fewer answers than were read
[Sentiment](/measure/sentiment) is measured only where an answer takes a position on your brand. Most answers that name a brand judge nothing, so the score usually rests on far fewer answers than were read; the page shows both counts. Competitors are not scored on the Sentiment page. It is a separate reading: share of voice counts a mention the same whatever the answer said about you.
## A country is not always a local search
Only the consumer surfaces (ChatGPT, Perplexity, Google AI and Gemini) are read from a browser in the country you track, and only in the [countries that can be read locally](/tracking/markets#which-countries-can-be-read-locally). **Claude is never read from a location.** It has no consumer reader, so its answers come from the API with the country stated in the question (`User location: DE`). The same holds for any run that fell back to an API. Such a rate says how the model answers someone who says where they are, not what a search from there returns.
In a [secondary market](/tracking/markets), these runs are labelled "location stated in the question" and never compared with a local reading, and a consumer surface that cannot be read from the country is shown as not measured rather than as zero.
## What we cannot attribute
We measure two things: what models answer, and which pages AI crawlers fetch. We do not measure what happened next.
Attensira cannot tell you that a mention produced a visit, that a citation produced a signup, or that any of it produced revenue. There is no identity join between an answer a model gave someone and a person who later arrived at your site. Anyone claiming that link for this class of product is inferring it, not observing it.
Treat these numbers as a measure of presence in AI answers. Connect them to commercial outcomes with your own analytics and your own judgement, and say out loud that you are doing so.
## Freshness: today is always partial
Work is enqueued on an hourly ticker, but each prompt × model × country combination runs **at most once per workspace-local calendar day**. Today's numbers are therefore incomplete until the day closes, and the shape of that incompleteness is not uniform — some prompts will have run, others not.
Failed runs are stored but excluded from `n`, so a day with collection problems produces a smaller sample rather than a distorted rate. Compare whole days against whole days. A "drop" measured at eleven in the morning is usually the morning.
For how to read a number once you have it, see [Reading the numbers](/measure/reading-the-numbers). For specific symptoms, see [Troubleshooting](/reference/troubleshooting).
# Troubleshooting
Source: https://docs.attensira.com/reference/troubleshooting
Answers to the questions people ask when an Attensira number looks wrong or missing.
Most surprising numbers in Attensira have an ordinary explanation. This page covers the five that come up most, in the order people usually hit them.
## Why is my share of voice zero?
Check first whether it is a measured zero or an unmeasured null. A measured zero has `n > 0`: runs completed and none named your brand. A null has no runs behind it and means nothing was collected. They look similar on a chart and mean opposite things.
If it is a measured zero, the models genuinely are not naming you for those prompts. That is a real finding, and usually the most useful one you will get in your first weeks. Look at which prompts produced it: broad category prompts ("best project management tool") behave very differently from prompts that already contain your name.
If it is a genuine zero but you expect otherwise, read a few stored answers. The verbatim answer text is kept for every successful run, and it will show you whether the model named competitors instead, refused the question, or answered something adjacent to what you meant to ask.
Also confirm your brand name and domain are entered as models would write them. Detection is a whole-word match, so a brand recorded with punctuation or a trailing suffix that models never use will match nothing. [Limitations](/reference/limitations) explains exactly how the match works.
## Why did my number not change?
Because the change did not clear the significance gate. Every delta runs a two-proportion z-test at 95%. If the movement sits under the noise floor, you get `{real: false, value: null}` — "no change we can prove". That is not a bug and it is not a zero; it is the system declining to report noise as signal.
This is most common at n=1, the sampling depth on Starter. One draw per prompt per model per day is a thin sample, and models are not deterministic, so ordinary run-to-run variation swamps small real movements. Widening the comparison window gives the test more runs to work with and is the cheapest fix.
If you routinely need faster confirmation of small movements, sampling depth is the lever: Growth, Business and Agency sample at n=3. See [Plans](/account/plans) for what that changes.
Also check you are not comparing a partial day against a whole one. Readings run at most once per workspace-local day, so today is incomplete until it closes. [Reading the numbers](/measure/reading-the-numbers) covers window choice.
## Why does one model show no data at all?
Almost always because it was never queried. Per-model metrics carry two flags. `tracked: false` means that model is not configured for your workspace, so no request was ever made. `readable: false` means no reader exists for that surface, so we cannot collect it regardless of your settings.
A new workspace starts with four model surfaces tracked (ChatGPT, Perplexity, Claude and Google AI), not all five. Gemini, on Growth and above, is untracked until you switch it on. Turn on the surfaces you care about and data starts arriving on the next day's run.
The `readable: false` case is different and you cannot fix it from settings: `google_ai` has no API reader, for example. Neither flag means the model rejected you. Reporting an untracked model as 0% would invent a failure that never happened, so we report it as unmeasured instead. See [Limitations](/reference/limitations).
## Why don't I see data yet?
Give it a full workspace-local day. An hourly ticker enqueues work, but each prompt × model × country combination runs **at most once per calendar day** in your workspace's timezone. A workspace set up this afternoon has its first meaningful comparison tomorrow, not in an hour.
Before that first day closes, partial data is expected: some prompts will have run and others will not, so early rates are computed from whatever landed. Do not read trends into the first twenty-four hours.
Check the ordinary setup causes too. Prompts must exist and be tracked, at least one model surface must be turned on, and your brand and domain must be set — without them there is nothing to match answers against. If your credit balance is at zero, agent-driven actions such as adding prompts will have been refused rather than partially completed; see [Credits](/account/credits).
Failed runs are stored but excluded from `n`, so a collection problem shows up as a smaller sample rather than a wrong rate. A day with unexpectedly low `n` is worth a look.
## Why did a prompt stop being tracked?
The two usual causes are that someone removed it, or that your workspace is at its prompt slot limit. Slots are a fixed stock — 50 on Starter, 150 on Growth, 350 on Business, 750 on Agency — and adding a prompt when the stock is full will not succeed until a slot is freed.
Removal is immediate and stops future readings for that prompt. History already collected remains available inside your plan's retention window: 3 months on Starter, 12 on Growth, unlimited on Business. Once a prompt's data ages past that window, it is gone, and re-adding the prompt starts a fresh series rather than restoring the old one.
Prompts can also be removed over the API. `remove_prompt` is a destructive tool available to any read-and-write key, and it does not ask for confirmation. If a prompt disappeared without anyone in your team removing it in the app, audit your keys in Settings > Your assistant and revoke any you cannot account for. See [API keys](/account/api-keys).
Re-adding is straightforward once a slot is free, but expect a gap in the series where the prompt was untracked. That gap is unmeasured, not zero.
# Concepts and entity model
Source: https://docs.attensira.com/start/concepts
Every word Attensira uses, what it means, and the wire field an MCP user actually sees.
Attensira uses a small vocabulary, and each word means exactly one thing. It comes in two layers: the words for the work the agents do for you, and the words for what we measure. This page defines every term, then maps the customer-facing words onto the field names you will meet in the API and through MCP.
## The work
**Chief of staff** — the one agent you talk to. It answers you, proposes goals and projects, hands work to the right crew, makes sure it gets done, and reports back. It does not do the work itself. See [How we work](/start/how-we-work).
**Project** — an area of work inside a workspace, with a crew of agents that keeps working on it: your blog, your site's technical health, the comparison questions. It has a short name, a focus, a feed of what happened and why, and optionally a goal, a deadline and a monthly cap. See [Projects](/agent/projects) and [Project and workspace](#project-and-workspace) below.
**Crew** — the agents staffed on a project (Chief of staff, Researcher, Writer, Web engineer and others), each with what wakes it. You describe the work; the Chief of staff staffs it. You never define an agent.
**Deliverable** — a finished piece of work: an article, a page, a fix, a social post, a listing fix, product copy or an image. Deliverables land in your own systems and are listed on the **Deliverables** page, with where each one lives and what it did. Articles, page fixes and outreach pitches are what a [budget](/account/plans#your-monthly-budget) commits us to deliver.
**Goal** — an objectively measurable target: a metric, where it is read from, a baseline, a target and a date. Each goal is read against a pace line and is **ahead**, **on pace**, **behind**, or **unmeasured** when its source cannot be read. The first goal is usually to be seen at all: named in a number of your prompts, on the assistants you chose. A goal reports where a number stands; it never claims the number moved because of us. Goals are under **Analytics → Goals**.
**Inbox** — where the agents put what needs you. Every item that asks something of you is one of three kinds:
* **Approve**: a yes or no on something the agents made. A draft can be edited before you approve it, and a decline asks why.
* **Answer**: a fact only you have, given in the row.
* **Do**: something only you can do, such as connecting an integration or sending a drafted email from your mailbox, then marked done.
Work already done on its own, or approved and moving, is shown too but asks nothing. The Inbox button in the top bar carries a count of what is waiting on you, and Home lists the top three under **Needs you**; both show the same number. See [the Inbox](/agent/inbox).
**Budget** — the monthly ceiling on what the agents' work may be charged, shown as the work it buys ("≈ 4 articles · 12 page fixes · 20 outreach pitches · daily monitoring"). It is separate from the plan fee, and from the plan's monthly response budget. See [Plans](/account/plans#your-monthly-budget).
## The tracked objects
**Prompt** — one question, phrased the way a buyer would phrase it. Your plan sets how many you can track at once: 50, 150, 350 or 750 slots.
**Topic** — a named group of related prompts, so you can read a whole buying question as one line instead of twenty.
**Model** — a model surface Attensira asks, identified by a stable channel id: `chatgpt`, `perplexity`, `google_ai`, `claude`, `gemini`. The channel is deliberately stable, so when the vendor swaps the underlying model behind that surface, your history stays continuous.
**Workspace** — the container for prompts, competitors, models, automations, and data. In the dashboard a workspace is called a **brand**: the brand switcher at the top of the sidebar picks which one every screen is about, and **Add a brand** creates another. A credential names your organisation and one workspace as its default; every MCP tool and REST endpoint takes an optional `project` to name another one in the same organisation.
**Answer** — one stored response from one Model to one Prompt on one day. Answers are the raw material every metric is folded from.
## What we read out of an answer
**Mention** — the answer named your brand in its text, with no link attached.
**Citation** — the model attached one URL, and that URL points at your own domain.
**Source** — a domain bucket across citations. Sources cover every domain the models cite for your prompts, not only yours, so you can see which sites the answers lean on.
Mentions and citations are counted independently. An answer can do either, both, or neither.
## The metrics
Every rate is `{value, n}`. `n` is the number of **successful** runs behind it; failed runs are stored but excluded. `value: null` means not measured. `value: 0` with `n > 0` is a real, measured zero.
**Share of voice** — successful runs naming your brand, over all successful runs.
**Citation rate** — successful runs citing your own domain, over all successful runs.
**Competitor rates** — the same calculation for a competitor, over the same denominator. They do not sum to 1, and they were never meant to.
**Source share** — citations to a given domain over all citations in the window.
**Average position** — the mean index of a URL inside the model's citation list, calculated **per source domain**. It describes where a domain tends to sit in a citation list. It is not a rank of your brand against competitors.
**Position** — where your brand first appears in an answer, counted among the names you track, always with its base: "#2 of 5 tracked names". See [Share of voice](/measure/share-of-voice#where-did-you-come-in-the-answer).
**Sentiment** — the stance of the answers that take a position on your brand, over those answers only. An answer that names you without judging you is left out, never counted as neutral. See [Sentiment](/measure/sentiment).
**State** — one word per prompt per engine, and one per prompt, for who had the answer: won, contested, lost or absent. See [Prompt state](/measure/prompt-state).
Deltas are `{value, real}` and pass a two-proportion z-test at 95%. Below the noise floor you get `{real: false, value: null}`: no change we can prove, which is not the same as no change.
Per-model figures carry `tracked` and `readable`. `tracked: false` means the model is not configured for this workspace and was never asked — reporting it as 0% invents a failure. `readable: false` means no reader exists for that surface.
There is no visibility metric and no ranking of brands. "Visibility" is a label in the interface, not a number. Position counts where you came among the names you track in one answer, and sentiment scores only answers that take a position; neither orders brands against each other. Share of voice here is a rate — runs naming you over all runs — not a slice of a total, and it does not sum to 1 across brands. The `share_of_voice` field carries exactly that rate. Sentiment never changes share of voice, where a mention counts the same whatever it says.
## The machinery underneath
**Credit** — the unit work is metered in: one credit is one cent at list price. Adding prompts, running an automation, and asking the agent all spend credits. Reading numbers you already have does not. See [Credits](/account/credits).
**Automation** — a saved piece of recurring work an agent performs on its own. A project's crew works through automations: one for each thing that wakes a crew member.
**Chat** — a one-off conversation with the Chief of staff about your workspace. Ongoing work belongs in a project.
**Agent model** — the LLM your chat runs on. Attensira picks it for each turn; there is no model picker. This is a different thing from a **Model**: an agent model answers you, a Model is a surface Attensira measures.
Credits and the response budget are currently inconsistent in the product. Treat credit arithmetic as approximate until that is fixed, and check your account before planning around a specific balance.
## Project and workspace
The two words are easy to mix up, and the wire makes it worse, so here is the rule.
A **workspace** is everything Attensira knows and does for one site: its prompts, competitors, models, connections, data, chats and automations, and its credit limits. Most organisations have one; an agency has one per client.
A **project** lives inside one workspace and is one piece of ongoing work in it, such as "win the comparison questions" or "keep the docs cited". It reads the workspace's data and works under the workspace's limits; it adds a crew, a feed, a focus and, optionally, a goal, a deadline, a monthly cap and an approval mode that can be stricter than the workspace's. A workspace can have many projects, or none.
On the wire, `project` has always meant the **workspace**: it is the `project` argument every MCP tool and REST endpoint takes, and `project_id` in identifiers. A project in the sense above is a `work_area_id`.
## Words to wire fields
The wire format kept its original names, and this is deliberate: renaming fields would break every existing integration, so the product vocabulary changed and the contract did not. When the docs and the JSON disagree on a name, both are correct.
| Customer word | Wire field | Appears in |
| - | - | - |
| Prompt | `keyword_id` | `get_prompt`, analytics rows, automation payloads |
| Topic | `group_id` | analytics rows, `get_analytics` with `area: "topic"` |
| Platform | `platform` | analytics rows, per-platform breakdowns |
| Workspace | `project_id` | internal identifiers; never a tool argument |
| Project | `work_area_id` | any payload that refers to a project |
| Inbox kind | `ask_kind` | Inbox rows, `list_inbox`: `approve`, `answer`, `do`, or `info` for a row that asks nothing |
The same holds for enum values. The `get_analytics` `area` enum accepts `visibility`, `prompt`, `page`, `source`, `competitor`, `topic`, `platform`, and `bot`. Those strings are the contract; pass them exactly as written. The older words `group` and `engine` were retired along with `channel` and are no longer accepted.
A **platform** is a stable identifier for an AI answer surface — ChatGPT, Perplexity, Google AI Overviews, Claude, Gemini. It is deliberately not the name of a model: the key survives the underlying model being upgraded, replaced or renamed, so two windows a year apart stay comparable.
## Crawlers are not models
An AI crawler fetches your pages. A model answers prompts. Attensira reports both, and they never mix: a crawler visit is not a reading, and a mention is not a pageview. See [AI traffic](/traffic/ai-traffic) for what the crawler side does and does not tell you.
# Your first fifteen minutes
Source: https://docs.attensira.com/start/first-fifteen-minutes
Give your domain, correct what the agents found, see what a free scan found, then choose a plan and a budget. Real work lands within fifteen minutes of paying, or you are told why.
You give one thing: your website. The agents read it and propose the rest, and you confirm or correct what they found. Nothing is charged before checkout, and you see what a free scan found before you are asked for a card. Everything is set up before you pay: your business, competitors, platforms, goals and the scan's first verdict are all on screen before the plan step asks for a card, and each step can be corrected in place. If you arrived from the [free audit](/start/free-audit), the questions it asked are kept word for word.
## Before you pay
The domain is asked once. If you came from the landing page or the [free audit](/start/free-audit), it is already filled in; otherwise the sign-up page asks for it. Google sign-in is the main way in, and email works too.
Creating the workspace starts the set-up on our side, in this order: the agents read your site into a drafted business profile, choose your starter prompts, and start the scan. You do not start any of it yourself. "Change domain" takes you back to the field; the old workspace is removed only once the new one has been created, so if creating it fails, your old workspace and its set-up are left as they were.
Each fact the agents found on your site arrives with the page it came from, such as "from /pricing". Edit any fact in place, and what you type is saved as confirmed. Anything your site did not say is asked as a question instead of being guessed. If your site cannot be read, the step says why and offers **Retry**.
Suggestions come from who appears in the scan's answers, such as "Named in 11 of 150 answers", then from domains cited beside yours, then from rivals named on your site. While the scan is still reading, the counts say "so far" and the step says how many answers are still to come.
Every suggestion starts ticked. Untick the ones you do not lose deals to and add missing ones by their website. When the agents are unsure whether a rival is in your market, they ask you about that one, and never about more than one. See [Competitors](/tracking/competitors#how-are-competitors-suggested-at-signup).
ChatGPT, Perplexity, Google AI Overviews and AI Mode, Gemini, Claude, Copilot, Google Search, YouTube, Reddit and review sites. The ones that make sense for you are already on, each with one line on why, worked out from what the scan's answers named and cited where it can be.
A surface we cannot measure for this workspace yet is still offered and still worked on. It says "Not measured yet" and never shows a number. YouTube, Reddit and review sites are measured through the citations in AI answers. At least one surface must stay on. Your choice is saved now and applied when your plan starts.
The first goal is always to become visible: named in more of the answers on each assistant you measure, with a target, a deadline 90 days out and a pace line. A target is never set below where the scan found you. Other goals follow when they apply: being recommended against the rivals you kept, ranking in Google once Search Console is connected, and a business number once Google Analytics is connected. The business number is shown beside the work and is never credited to it.
The prompts behind those goals are your starter set: about fifty, grouped into four to eight topics and collapsed. Open them to read or change them, or change them later from chat or MCP. See [Topics](/tracking/topics#the-starter-set). Goals are created when your plan starts.
The verdict, before the plan. See [the scan](#the-scan) below.
Starter, plus a monthly work budget shown as the work it buys. The step says the top-up rule in one sentence and you agree to that sentence before checkout. See [Plans](/account/plans#your-monthly-budget).
## The scan
The scan is free and runs before you hold a plan. It asks every starter prompt once (`n = 1`) on ChatGPT, Perplexity and Claude, in your primary market: at most 150 answers.
You see one row per assistant, each with its count, such as "12 of 50 read". Once half the answers are in, or twenty minutes have passed, the step writes the verdict: how many answers named you, out of how many, per assistant, such as "Named in 3 of 48". It says "so far" until the scan finishes. There is no comparison with anything; it is day one.
* A read that failed after retrying says "could not be read". It is never counted as a zero.
* An assistant you chose that the scan does not read (Google AI Overviews, Gemini or Copilot) says "Not measured yet" and has no number. Google AI Overviews is outside the free scan only: once your plan starts, Starter reads it with the others.
* You can go on to the plan at any time. The scan keeps reading on our side.
The free scan is limited per site and per account. If your site or account had one recently, the step says "Your scan starts when you pick a plan" and shows no number. When many scans are running at once, yours waits in line and starts on its own.
## After you pay
The screen reads back what is being watched (your domain, how many prompts and competitors) and the budget rule as it now stands, with the same numbers **Settings → Billing** shows. If the payment provider's confirmation takes a moment to reach us, the screen says it is waiting rather than sending you back to set-up.
### Watching it work
The moment your subscription is confirmed, every prompt is read on every engine your plan includes, and you watch it happen: one row per engine, updated every three seconds, each saying how many of its reads are in. If you pay on the same day (UTC) as your scan, the scan's answers count toward these readings and are not asked again; only what the scan did not cover is read. A read that failed after retrying says "could not be read yet". It is never counted as a zero.
Once at least half the reads are in (or twenty minutes have passed), you get the verdict, per engine, with the sample size in the sentence. See [Your first reading](/start/first-reading).
### The first thing to do
The [set-up run](/start/set-up-run) starts on its own. If your scan reached its floor in the last 48 hours, it starts the moment you pay; otherwise it starts when your first readings reach theirs.
When it files its first work, "First fix is ready" appears on the first item and you get an email. The first thing to do is a draft pull request if a repository is connected, or a before-and-after [rewrite](/agent/inbox#rewrites) with a shareable link and a one-click "request access from my developer" if not. GitHub is offered only when a fix is ready to become a pull request. Nothing is merged for you: pull requests open as drafts, and auto-merge is off unless you turn it on.
### Fifteen minutes, or the reason
Fifteen minutes after you pay, we check what the set-up run has filed. Real work in your [Inbox](/agent/inbox) is the goal. When it is not there yet, the screen says why, and the Inbox gets a row only when you have to do something:
| What we find | What you see |
| - | - |
| Work is filed | It is in your Inbox. |
| The run is still working | A line on the screen says what it is on. No Inbox row. |
| The run is waiting for your answer | A line on the screen points you to its question in your Inbox. |
| Your first readings are still coming in | A line on the screen says the run starts as soon as enough have landed. |
| The run needs credits, or your site could not be read | One **blocked** row in your Inbox saying what to do. |
| The run stopped without filing anything | One row in your Inbox that lets you start it again. We are alerted too. |
### Your team
Under the work is **Your team**: the [projects](/agent/projects) the Chief of staff proposes from what the set-up run finds on your site, each with the crew on it. Each one is yours to keep, pause or remove; the Chief of staff runs the rest. Projects appear here, and in the sidebar, as they start. Until then the section says **No projects yet**.
The kickoff call is offered here on every plan, whatever the team looks like. Book it when it suits you: nothing waits on it.
The last line is the only promise the screen makes: tomorrow at 07:00 you get your first [brief](/agent/morning-brief), if there is something to show.
## After that: Home
From then on, signing in opens [Home](/start/today#home). It asks "What should we work on?" above the box you hand work to, and under it, **Needs you** lists the three things that most need your answer, approvable in place. How many are waiting is the same number as the badge on the Inbox button in the top bar.
# Your first reading
Source: https://docs.attensira.com/start/first-reading
What happens the moment your subscription goes active: a dedicated pass reads every tracked prompt right away, and files what it finds in your Inbox.
Paying for Attensira starts a read, not a wait. The instant your subscription becomes active, every prompt you are tracking is read on every model your plan includes — on a dedicated fast lane that runs once, separately from the daily schedule that takes over afterward.
## What you see while it runs
The onboarding screen shows this pass live: one row per prompt on each model, resolving from waiting to read as the results come back. Nothing here is a placeholder — every row is a real, individually queued read, and the screen is watching the same record the API does.
Not every read lands cleanly. A model can time out, refuse a request, or return something we cannot parse. Reads like this are retried, and a row that could not be read after retrying **says so** rather than sitting there forever or quietly counting as a zero. "3 could not be read yet" is a fact about that row, not an estimate.
## When the setup pass reports in
You do not wait for every last row. The setup pass reaches a floor and reports what it has as soon as **at least half the reads are in**, or after **twenty minutes**, whichever comes first — a customer who paid a minute ago is owed an answer, even a partial one. What it finds is filed in your [Inbox](/agent/inbox), so the first thing this subscription does is hand you something to look at, not a spinner.
A full setup pass like this — reading every tracked prompt across the whole plan at once — runs **at most once a day per workspace**. Re-activating a subscription you had just turned on again the same day does not queue a second one.
## After that
Once the setup pass has run its course, your prompts move onto the same daily schedule as every other workspace: each prompt × model × country combination is read once per workspace-local day, at the sampling depth your plan sets. See [How we collect data](/measure/how-we-collect-data) for the full mechanics of that ongoing cadence.
# The free audit
Source: https://docs.attensira.com/start/free-audit
What attensira.com/audit reads, what it shows everyone, and what one email address unlocks.
The free audit at [attensira.com/audit](https://attensira.com/audit) is the trial. Enter a domain and it asks the same four engines a Starter workspace tracks (ChatGPT, Perplexity, Claude and Google AI Overview) eight buying questions about your category, and tells you who got named.
## What it reads
Eight questions are proposed from your site. At least three are implementation-shaped ("how do I…", "X vs Y") because those are the questions buyers ask when they are close to choosing. You can edit, delete or add questions before the run, up to eight. Each engine answers each question once, so every number on the report carries its n.
## What everyone sees
The verdict is public: per engine, how many of the eight questions named you and how many named a competitor instead. When you are named in most, the report leads with that. Competitor statements appear only as the model produced them; anything we cannot show safely is withheld and says so, and every report carries a takedown link.
Reports are not listed anywhere and carry `noindex`. There is no directory of audited domains.
## What one email unlocks
One email field opens two things:
* **The deciding-sources map.** For each losing question and each engine, the pages and domains that decided the answer, whether you are on them, and the next move.
* **One rewrite of one of your own pages.** We take the question you lost most and the page that should have answered it (one of your pages the assistants already cited, or your homepage), read it at that moment, and write one passage of it beside a replacement. Two checks run in code, the same two the paid product runs on every change: the passage must be on the page exactly as we read it, or the rewrite is refused and the report says so; and the change is classified **safe** (touches nothing you could be held to) or **claim** (touches a number, price, promise, product or competitor name), labelled that way so you read it before publishing. Code samples are never rewritten.
Nothing is changed on your site. You copy the text or share the report.
If you give an email address with the domain, we send one email when the report is ready, with its link. That is the only email the audit sends, and asking for a report does not sign you up for a newsletter.
## Limits
One rewrite per audit. A finished report stands for 30 days; opened after that, it is shown with its date while a new run replaces it at the same link, so a shared link never breaks. On a busy day the report can be generated without the map or the rewrite; it says so rather than showing something older or invented. Runs are capped per address and per day.
If a run fails, or stops making progress for 90 minutes, entering the same domain again starts a new run at the same link. A retry counts against the same per-address and daily caps as a first run.
## From the audit to a workspace
"\$129/mo. It does this every morning and starts working the map." The button carries your domain and your eight questions into the [first fifteen minutes](/start/first-fifteen-minutes) of a workspace, so nothing is typed twice.
# How we work
Source: https://docs.attensira.com/start/how-we-work
An SEO agency run by AI agents: who does what, what reaches you, what needs your yes, and what we promise and what we don't.
Attensira is the AI-native SEO agency. SEO here means search everywhere: Google, the AI assistants (ChatGPT, Claude, Perplexity, Gemini and Google's AI), and the sites and communities those assistants read. The agents do the work. You set the direction, answer the few questions only you can answer, and approve what goes live.
No person on our side does client work. Research, writing, page fixes, outreach drafts, follow-ups and the weekly report are done by agents. A kickoff call is part of onboarding on every plan, the way a good agency starts, but nothing waits on it: checkout, the first readings and the first piece of work start without it.
## Who does what
| Who | What they do |
| - | - |
| **You** | Set goals, steer, approve or decline what the agents propose, answer their questions, and do the few things only you can do, such as merging a pull request or sending an email from your own mailbox |
| **Chief of staff** | The one agent you talk to. It answers, proposes goals and projects, hands work to the right crew, makes sure it gets done, and reports back. It does not do the work itself |
| **A project's crew** | The agents that do the work for one [project](/agent/projects): a Researcher, a Writer, a Web engineer and the others the Chief of staff staffs it with. Each one wakes when something it is responsible for changes |
| **Your own assistant** | Optional. Connected over [MCP](/mcp/overview), it can set up a workspace, read the numbers and act, with the same data and the same approvals as the dashboard |
You never define an agent, write a schedule or pick a team. You describe what you want in your own words and the Chief of staff proposes how it will be done, as a card you accept or change.
## Where you talk to it
* **Home.** Type what you want. Leave it to the Chief of staff to route, or pick the project it belongs to.
* **A project's feed.** **Message** asks the crew something or hands it work. **Steer** sets direction ("focus on enterprise, stop the SMB posts"), and the steer is kept word for word as the project's direction. See [Projects](/agent/projects).
* **The Inbox.** Everything that needs you.
## What reaches you
The agents stop only when they need you, and when they do, it lands in the [Inbox](/agent/inbox) as one of three kinds of item:
| Kind | What it asks | What you do |
| - | - | - |
| **Approve** | A yes or no on something the agents made: a change to your site, an article, an action that cannot be undone | Approve it, edit a draft and then approve it, or decline it and say why |
| **Answer** | A fact only you have: a question an agent asked, or a gap in your business profile | Answer in the row |
| **Do** | Something only you can do: connect an integration, send a drafted email from your mailbox, paste an article where it goes | Do it, then mark it done |
Work the agents did on their own, and work already approved and moving, is shown as well, so you can see it, but it asks nothing of you.
The agents also report without being asked: a short [brief](/agent/morning-brief) on mornings when there is something to show, and, every week, each project's Chief of staff posts what its crew did and what moved.
## Nothing irreversible without your yes
Every action an agent can take is classified by whether it can be undone. Reversible work, such as opening a draft pull request or drafting an article, can happen on its own. Anything that cannot be taken back stops for your yes. How much happens before that stop is your workspace's [approval mode](/agent/approval): ask, draft or act. A project can be set stricter than its workspace, never looser.
Two kinds of action stay yours unless you hand them over:
* **Merging.** We open pull requests; you merge or close them. We merge only if you switch on [auto-merge for safe changes](/agent/approval#auto-merge-for-safe-changes), and then only safe changes such as structured data, meta tags, alt text and internal links.
* **Sending.** Outreach goes from your own mailbox, never ours. A pitch is drafted into your Drafts for you to send. The agent can send pitches for you only in act mode, and only after the first ten have gone out by hand. See [Off-site work](/agent/off-site).
## Where the work lands
Work lands in your own systems, where you already review things: a pull request on your repository, a draft in your CMS or Notion, a pitch in your mailbox, or text to paste where there is no connection. Everything that was made is listed on **Deliverables**, with where it lives and what it did.
Every change is read before and after it ships, on the same questions and the same assistants. See [Before and after](/measure/before-and-after).
## What we promise, and what we don't
**We promise the work.** Your plan and your monthly budget buy a volume of work: articles, page fixes and outreach pitches. If we deliver less than the budget committed, the work we did not deliver is not charged. Work that is finished on our side and waiting on you, such as a pull request to merge or an article to publish, counts as delivered: it is never a shortfall. See [Plans](/account/plans#if-we-deliver-less-than-the-budget-committed).
**We don't promise outcomes.** No one can promise what an assistant or a search engine will say, and whether a change works also depends on it being published. We do not guarantee rankings, citations, mentions or traffic. We measure every change and tell you what it did, including "nothing moved that we can prove" when that is the honest answer. See [Limitations](/reference/limitations).
## What you pay for
A plan fee for the platform, and a monthly budget for the work, shown as the work it buys: "≈ 4 articles · 12 page fixes · 20 outreach pitches · daily monitoring" for a \$1,500 budget. The budget is a ceiling, not a prepayment: work is charged as it is done, new work stops when the month reaches the budget, and **Costs** shows where every dollar went. See [Plans](/account/plans) and [Costs](/account/costs).
# Quickstart
Source: https://docs.attensira.com/start/quickstart
From your domain to a team at work: the set-up steps, the plan and budget, the first readings, and how to do it from your own assistant.
Signing up asks for one thing, your website. From the domain, the agents read your site, choose the questions to track, find your competitors and run a first scan, and each set-up step shows what they found with the answer already filled in. Most of set-up is reading it and pressing **Continue**. Then you choose a plan and a monthly budget, and the team starts work.
## Set up in the dashboard
Enter it on the sign-up page, or arrive from the [free audit](/start/free-audit) with it already filled in. Submitting it creates your workspace and starts set-up on our side: reading the site, picking the starter prompts and running a preview scan. If you came from the audit, the questions it asked are kept word for word.
What we read from your site, each fact with the page it came from. Correct any of them in place. What the site did not say is asked as a question rather than guessed. An unanswered question never blocks you: it is asked again later.
The rivals that actually show up in the answers, filling in as the scan reads. Every suggestion starts ticked: untick the ones you do not lose deals to, and add any that are missing. Only the ticked ones are tracked. See [Competitors](/tracking/competitors#how-are-competitors-suggested-at-signup).
The assistants, Google, YouTube, Reddit and review sites, with the sensible ones already on and a line on why each one matters for you. A surface we cannot measure for your workspace yet is still offered and still worked on, and says **not measured yet** rather than showing a number.
The first goal is about being seen at all: named in a number of the prompts that matter, per assistant, with the baseline the scan read and a date. Other proposed goals are switches you turn on or off. The prompts behind the goals are your starter set: about fifty, grouped into four to eight [topics](/tracking/topics#the-starter-set). Read them and remove or reword the ones that are off, now or later from the prompts screen, chat or MCP.
The [free scan](/start/first-fifteen-minutes#the-scan) asks each starter prompt once on ChatGPT, Perplexity and Claude. One row per assistant, with how many of its answers are in, and once enough are, how often they named you. An assistant we cannot read yet says so; it is never shown as a zero. You can continue at any time: the scan keeps running.
The plan runs the platform. The budget buys the work, and is shown as the work it buys, for example "≈ 4 articles · 12 page fixes · 20 outreach pitches · daily monitoring". Beside it: work we don't deliver isn't charged, and items waiting on you to publish count as delivered.
At the budget, new work pauses and you are asked first, or you can let it run up to 10% over. The button agrees to one sentence with three numbers in it, such as "We'll top up $50 when your balance falls below $10, never more than \$1,500 a month." Nothing is charged by agreeing: payment is taken at checkout, and the budget rule starts when your plan does. You can change the budget or top up any time in **Settings → Billing**. See [Plans](/account/plans).
After checkout, the first readings start straight away, the first piece of work follows, and the projects the Chief of staff proposes for you appear under **Your team**, each one yours to keep, pause or remove. You can also book the kickoff call there. See [Your first fifteen minutes](/start/first-fifteen-minutes).
## Set up from your own assistant
Your own assistant can do the same set-up over the [MCP server](/mcp/overview). Add the server to your assistant and sign in through the browser when it asks (see [OAuth](/mcp/oauth)); you need no workspace first, because the first call makes your account's organisation. Then ask it to set up your business. Once you are set up, **Settings → Your assistant** connects more assistants.
1. `start_workspace` takes your domain and creates the workspace, or resumes it. It returns the facts we read, the questions we have, the suggested competitors and platforms, the proposed goals and the plans. Calling it again with the same domain is how the assistant checks on progress; it never creates a second workspace.
2. `confirm_setup` saves your choices (plan, monthly budget, competitors, platforms, goals and profile corrections) and returns a checkout link. The plans it can take are the ones `start_workspace` listed: every plan checkout sells, not only Starter.
3. You open the link and pay in your browser. **An assistant cannot charge you**: every payment finishes in a browser, with you looking at the price. If you already have a card on file, the link opens your billing page instead, so the charge is still one you make yourself.
4. Once you have paid, `start_workspace` reports the workspace as active, and then when its first readings are ready.
Until an account has paid, it has one workspace: `start_workspace` with a second domain is refused until the first is paid for, so an assistant cannot leave a trail of half-set-up workspaces.
Items your assistant files in the Inbox are marked with where they came from, such as **via Claude**, so you can tell them from the ones the team raised. The same approvals apply to everything it does.
## Reading the first numbers
The moment your plan starts, your [first reading](/start/first-reading) reads every prompt on every engine your plan includes, on a fast lane of its own, and its first answers land within minutes. A prompt you add later is queued for its first reading when you add it. After that, readings run on a daily cadence: each prompt × model × country combination is read **at most once per workspace-local calendar day**.
Open **Analytics** in the sidebar, then its **Overview** tab. Two numbers matter first. **Share of voice** is the share of successful runs that named your brand. **Citation rate** is the share of successful runs that attached a link to your own domain. They answer different questions: whether the model knows you, and whether it sends readers to you.
Every rate arrives as a value plus `n`, the number of successful runs behind it. A value of `0` with `n > 0` is a real measured zero. A **null** value means not measured: no successful run exists for that cell, and it is not a zero. Per-model numbers also carry `tracked` and `readable`; a model with `tracked: false` was never asked, so it is not a 0%.
Sampling depth is the number of times each prompt × model is asked per reading: `n = 1` on Starter, `n = 3` on Growth, Business and Agency. Changes over time pass a two-proportion z-test at 95%. Below that noise floor you get `{real: false, value: null}`, which means "no change we can prove", never zero.
Do not read the first day as a trend. With `n = 1` a single day is one draw per prompt; rates only mean something once several days have accumulated.
## What next?
Ask the agent why a number looks the way it does, then hand the recurring parts to an Automation. Both start from the box on [Home](/start/today#home), under "What should we work on?"
When the agent needs you — an approval, a decision, a connection only you can make — it says so in two places: the **Inbox** button in the top bar, whose badge counts what is waiting, and **Needs you** on Home, which lists the top three. Both show the same number. See [Home and the dashboard](/start/today) for where everything else lives.
Word-to-field mapping, and why the wire format still says `keyword_id`, `group_id` and `work_area_id`, is on [Concepts](/start/concepts).
# The set-up run
Source: https://docs.attensira.com/start/set-up-run
The one session that runs itself when your plan starts: it reads your site, groups what is wrong by the source that decided it, and files a starting plan.
## What it is
One session starts on its own, without you asking for it. It is the first thing the product does with your money, and the only run allowed to look at a workspace it has never seen before.
It runs **once, ever**, per workspace.
## When it starts
* **At payment, when your scan already has enough.** If the free [scan](/start/first-fifteen-minutes#the-scan) you saw before paying reached its floor in the last 48 hours, the set-up run starts the moment your plan does. Its floor here is at least half the scan's answers read, or every read finished with at least one answer. A scan that read nothing never starts it.
* **Otherwise, at your first reading's floor.** When there was no scan, it was older than 48 hours, or it did not read enough, the run waits for your [first reading](/start/first-reading) to reach its floor: **at least half the reads back**, or twenty minutes after the reading started.
Either way it is one session. The first-reading floor arriving later does not start a second one.
Fifteen minutes after you pay, we check what the run has filed. If nothing is filed yet, you are told why: on the onboarding screen while it is still working, or with one row in your Inbox when you have to do something. See [Fifteen minutes, or the reason](/start/first-fifteen-minutes#fifteen-minutes-or-the-reason).
## What it does
1. **Reads your site.** The home page and whatever pages answer a buying question — never a claim about a page it did not actually fetch.
2. **Reads the losing prompts** from the readings so far (your scan's or your first reading's): where you were not named, on which engine, against which rival.
3. **Groups what it finds by deciding source** — the same grouping [Who decides an answer](/measure/deciding-sources) uses — so a domain behind four losses is one piece of work, not four.
4. **Files at most three rows** in your [Inbox](/agent/inbox), each naming one source and the decision that is yours to make, backed by a verbatim excerpt from the answer.
5. **Proposes one fix as a pull request**, when a repository is [connected](/agent/github) — the single best on-site change among the sources it found. Never more than one; a first session that opens four pull requests is a first session nobody merges.
## If it finds nothing wrong
A workspace with nothing to fix does not end with an empty run. Instead, the session takes your **worst prompt** — lowest rate, largest sample — and files one plan for winning it: the page that should answer it, the claim that page is missing, and the evidence behind each part of that plan.
A set-up run never ends with nothing filed.
## If credits cannot cover it
The balance is checked before anything is read or dispatched. If your workspace cannot afford the run, nothing is read and nothing is charged — but the refusal is never silent. It is written down three ways: a **blocked** row in your Inbox, an email, and a session you can open that says exactly why it did not start. Top up, and the set-up run can be started again.
## What it never does
* It never reports a trend. There is one day of readings and nothing to compare it to yet.
* It never lists every page it read or every prompt you are named on — only what needs a decision.
* It never files more than three Inbox rows, and never fewer than one.
This is a once-ever run. The standing job that keeps watching after it is the hourly pass described in [What the agent notices on its own](/agent/findings).
# Home and the dashboard
Source: https://docs.attensira.com/start/today
The signed-in home: what to work on next and what needs you. Plus where everything else lives, in the sidebar and the top bar.
## The sidebar
The dashboard has one sidebar. At the top is the **brand switcher**: the brand (workspace) every screen is about, shown by its domain. Its menu switches brand, adds one (**Add a brand**), and opens **All workspaces**, the overview of every workspace in your organisation (see [Clients](/agencies/clients)). When you belong to an organisation, the same menu switches, manages and creates organisations.
Under it, five rows:
* **New**: [Home](#home), at `/`. Starting something is what you open it for, so the row is named for that.
* **Search**: opens the command menu. Cmd-K (Ctrl-K) does the same from anywhere.
* **Analytics**: everything tracked, as one row of tabs: **Goals**, **Overview**, **Prompts**, **Pages**, **Sources**, **Competitors**, **Boards** and **Site health**.
* **Deliverables**: every article, page change and post the agents made, where it lives and what it did.
* **Customize**: what the agents work with, as tabs: **Connectors**, **Skills**, **Guardrails**, **Agents** and **Company**.
Then your chats: pinned ones first, then the most recent, and **All chats** for the rest (`/s`). Cmd-N (Ctrl-N) starts a new one from anywhere.
**Bot traffic** is not in the sidebar. Open it from the command menu ("Bot traffic") or at `/analytics/ai-traffic`. See [AI traffic](/traffic/ai-traffic).
Older addresses still work. `/knowledge` opens Customize on **Company**, and `/plugins` opens it on **Connectors**.
## The top bar
The bar above every screen stays put while the page scrolls under it. Left to right:
* **Search**, the same command menu as the sidebar row.
* **Inbox**, with a badge counting what is waiting on you. It counts decisions, not rows: seven runs stuck on one missing connection are one thing to do, and count as one. Chats paused on a question for you count too. The badge turns amber once the oldest item has waited more than three days, and hovering it says how long, for example "4 things waiting on you, oldest waiting 5 days". See [Inbox](/agent/inbox).
* **Usage**: your credit balance, and on wider screens the prompts you track and the responses read this month. Open it for those against your plan's limits, **Buy credits**, and **Upgrade**. It turns amber when the balance is low or a plan limit is used up. See [Credits](/account/credits) and [Plans](/account/plans).
* **Settings**, the gear.
* **Your account**: Settings, Billing, Help & support, Docs, and Sign out.
On a phone, the sidebar opens from the menu button at the left of the bar, Search shrinks to its icon, and Settings is in the account menu.
## Home
Home asks **What should we work on?** and gives you the box to answer in. Everything on it is one of two things: pointing the agents at work, or answering what only you can answer.
When the [loop](/agent/loop-health) is blocked, one line says so and why, for example "Attensira is paused: GitHub is not connected", with **Fix it**. When everything runs, there is no line at all.
Ask a question, or ask for work. `/` picks a mode (Plan, Ask or Report), and the microphone dictates. See [Chats](/agent/chats#the-box) for everything the box does.
Under the box, a few questions drawn from your own numbers. Picking one puts it in the box for you to read and edit. It is never sent for you. They step aside as soon as you type.
The three things that most need you, with how many are waiting beside the heading. That number is the same one the Inbox badge shows. See [Needs you](#needs-you) below.
### Needs you
The list leads with the same three decisions the [Inbox](/agent/inbox) leads with, in the same order, most consequential first. A chat paused on a question for you is listed after them. Each row works in place: approve or decline a change, read a written piece, or connect the integration a group of rows is waiting on, without opening the Inbox.
Under the three:
* **N more waiting · Open the Inbox**, naming what the rest are waiting on when that is a connection, for example "including Connect GitHub".
* **N in progress, nothing needed from you**, when chats are running.
When nothing is waiting, it says **Nothing needs you**. If what is waiting could not be read, it says **Couldn't load what's waiting** with **Retry**, and keeps the last count it read. It never shows an empty list in place of a failed read, because "nothing needs you" is the one wrong answer that list could give.
Act on a row here and the next one moves up; the count beside the heading and the Inbox badge change together.
## After checkout
Returning from checkout lands on one page: **Checkout cancelled** or **Payment received**, with a button back to Home.
# What Attensira does
Source: https://docs.attensira.com/start/what-it-does
Agents do the SEO work toward goals you set, and every change is measured against what real assistants answer.
## What problem does it solve?
Buyers now ask an assistant before they open a search engine, and the assistant offers two or three options. If you are not one of them, you are not considered. Three things stop most teams acting on that:
1. **The numbers are opaque.** Share of voice, citation rate, sentiment: it is hard to tell which one should change what you do this week.
2. **The tools need an operator.** Dashboards say what is wrong and leave the fixing to you.
3. **Nobody has the time.** Even a correct recommendation sits untouched, because acting on it is a week of work nobody scheduled.
Attensira takes the job. Agents do the work, show every piece of it, and measure what it did. You set the direction and approve what goes live. See [How we work](/start/how-we-work) for who does what.
## How the loop works
At signup the agents read your site and propose goals. The first is usually to be seen at all: named in a number of the questions that matter to you, on the assistants you chose, by a date. Each goal has a metric, a baseline, a target, a date and a pace line it is read against.
The Chief of staff proposes [projects](/agent/projects), each a crew of agents on one area of work, such as your blog or the comparison questions. You accept, change or remove them. You never define an agent or write a schedule.
Pull requests to your site, articles, page rewrites, directory listings, community replies and outreach drafts. Every morning the [Daily win plan](/agent/autopilot) also does up to three pieces of work toward the questions you want to win.
Anything that cannot be undone waits for your yes in the [Inbox](/agent/inbox), along with the questions only you can answer and the few things only you can do. Work lands in your own systems: your repository, your CMS, Notion, your mailbox.
Each day we put your prompts to the assistants you track and store the answers. Every change is read before and after it ships, and each goal is read against its pace line. A goal that falls behind is re-planned.
## What we measure
A **Prompt** is one question, phrased the way a buyer would phrase it. Each prompt runs against each **Model** you track, and every **Answer** is stored and read for two separate things, for you and for each competitor you track: whether the brand was named, and whether the model attached a link to the brand's domain. These are real answers from live model surfaces, not predictions and not a simulation.
### Mention versus citation
A **Mention** means the answer named your brand in its text, with no link. A **Citation** means the model attached one URL pointing at your own domain. They are counted independently: an answer can mention you without citing you, cite you without naming you in prose, do both, or do neither.
That distinction matters. A mention means the model knows who you are. A citation means the model sent the reader to your page. The two move for different reasons and are fixed by different work, so Attensira never collapses them into a single score.
Share of voice is the share of successful runs that named your brand. Citation rate is the share of successful runs that cited your own domain. Competitor rates use the same denominator, so they will not add up to 100%.
A **Source** is a domain bucket across all citations in a window, including domains that are not yours. It tells you which sites the models lean on when answering your prompts.
## What it does not do
* **It does not guarantee outcomes.** We promise a volume of work for your budget and do not charge for work we don't deliver. No one can promise what an assistant or a search engine will say. See [Plans](/account/plans#what-a-budget-does-not-buy).
* **It does not claim revenue.** We show what changed in the answers and in your traffic beside the work we shipped. We never claim a sale was caused by it.
* **No person on our side does client work.** The agents do it, and a person is asked only for what a person has to decide or do.
* **It does not rank brands against each other.** Your position counts where you came among the names you track in one answer, and there is no share-of-voice figure that sums to one.
* **It does not count an answer that names you without judging you as neutral sentiment.** Sentiment reads only the answers that take a position on you.
* **Crawler traffic is a separate measurement.** A crawler fetches pages and a model answers prompts; those are separate systems, read separately.
# What to expect in your first eight weeks
Source: https://docs.attensira.com/start/what-to-expect
What you will see in weeks 1, 2, 4 and 8, in the order the signals can honestly appear: shipped work, crawler fetches, Search Console impressions, then citations and goals.
Before you pay, the plan step at signup shows four steps under the plan and budget: what you will see in weeks 1, 2, 4 and 8. The same four steps come back in your Monday note while the workspace is new, so the expectation you were given at signup is the one you are measured against afterwards.
The steps describe what we measure and report. They are not results we promise: what you pay for is the work, and work we don't deliver isn't charged. What the engines then do with it is something we measure, not something we control.
## The four steps
The agents ship the first changes, and the Monday note lists each one with a link.
We count how often AI crawlers fetch each changed page after it goes live.
Search Console starts to report impressions for the changed pages, about two days behind.
We report whether answers name or link you for the first time, and how each goal is pacing.
## Why it runs in this order
Each step is the earliest point at which that signal can honestly appear.
* **Work comes first** because it is the one thing fully in our hands. A pull request opened, a page rewritten, a pitch drafted: each is listed with a link the week it ships, and the same acts are on your [receipt](/account/receipt).
* **Crawler fetches come next** because a crawler has to fetch a changed page before an engine that searches the web can quote the new version. These are counts of fetches, not of answers. See [AI traffic](/traffic/ai-traffic) for why a fetch and a citation are different things.
* **Impressions follow** because Search Console reports with a delay of about two days, and a new or changed page takes time to be shown at all.
* **Citations and goal pace come last.** Answers change slowly, and some come from what a model learned months ago, so a citation or a goal moving is reported as what it is and never presented as the result of the work.
## Where you see it
**At signup.** On the plan step, under the plan and the budget, before you go to checkout, headed *What you'll see in your Monday note*. No week is marked yet.
**In the Monday note.** The Monday note carries the same four steps under *What to expect in your first eight weeks*, with the line "You are in week N". In the app, the step you have reached is also marked; the email lists the steps in order. The weeks count from the day the workspace was created, not from payment: week 1 is its first seven days, and the timeline stops showing after day 56. The first Monday note a workspace receives always carries the timeline. If that note comes after the eighth week, it shows the steps without a week number.
The signup page and the note use one shared set of steps, so they cannot describe different timelines. The note is the Monday edition of the [morning brief](/agent/morning-brief).
## When a signal cannot be measured
Two of the steps depend on something being connected. When it is not, the note says why in words, never with a zero that could mean "nothing happened".
| Step | Needs | Without it, the note says |
| - | - | - |
| Crawler fetches | The [AI traffic](/traffic/ai-traffic) crawler snippet installed on your site | We cannot count AI crawler fetches yet, because the crawler snippet is not installed on your site. |
| Impressions | Search Console connected to the workspace | Search Console is not connected, so impressions are not counted. |
If a change went live too recently for Search Console's reporting delay, the note says the change is still inside that delay. It does not print a zero.
Shipped work and citations need nothing extra. Citations come from the answers we already read for your [prompts](/tracking/prompts). Goal pace is shown for the goals your workspace has.
## Related
* [Your first fifteen minutes](/start/first-fifteen-minutes): the scan, the verdict and the first fix.
* [Before and after](/measure/before-and-after): how a change is measured once it ships.
* [Reading the numbers](/measure/reading-the-numbers): sample sizes, unmeasured cells, and when a change is real.
# Brand family
Source: https://docs.attensira.com/tracking/brand-family
The other domains, products and sub-brands you own count as you once you confirm them. Agents propose them with evidence, and prefer publishing on your own domain.
## What counts as you?
Your workspace's domain counts as you, and so does **every subdomain of it**. An answer that cites `blog.example.com` has always counted as citing `example.com`. Nothing needs setting up for that.
The **brand family** is everything else you own: a second domain, a product with its own name, a sub-brand. Once you confirm one, an answer that names it, or cites a page on its domain, counts as you. That applies to every headline number: share of voice, citation rate and position. A confirmed property is never listed as a third-party source, a deciding source, an outreach target or a new competitor.
| Kind | What it is | How answers are matched |
| - | - | - |
| `subdomain` | A host under your own domain, such as `blog.example.com` | Already counted as you. Confirming one only names it, so the [split](#how-much-of-you-each-part-is) can show how much of "you" it carries |
| `domain` | Another domain you own, such as `hello.com` | Citations of the domain and of any subdomain under it (`blog.hello.com`). Not the other way round: owning `shop.hello.com` does not make the rest of `hello.com` yours |
| `product` | A product name, such as "Acme Analytics", with an optional domain of its own | The name in the answer text, matched the way your brand name is, and citations of its domain when it has one |
| `sub_brand` | A sub-brand name, with an optional domain | As for a product |
### What a property cannot be
* **Your own domain, or anything under it, as a `domain`.** It already counts as you. A host under your domain is added as a `subdomain`.
* **A tracked competitor's domain.** One domain cannot be both you and a rival, so it is refused with `property_is_competitor`. If it really is yours, remove it from [Competitors](/tracking/competitors) first.
* **A name shorter than three characters, a placeholder, or a common word.** "Analytics", "Cloud" or "Studio" would make every answer about the subject an answer that named you. Name a product the way the answers would, such as "Acme Analytics".
* **More than 50 properties** in one workspace, counting proposals and rejections.
A product name that is also an everyday phrase can still over-count, as a brand name can. A confirmed name is matched the way your brand name is, so confirm only names the answers use to mean you.
## Only confirmed properties count
A property is **proposed**, **confirmed** or **rejected**. Only a confirmed one changes a number. A proposed one is a question an agent is asking you, and a rejected one is kept so the question is not asked again.
Every confirmed property records the person who confirmed it. Nothing is confirmed automatically, and an agent cannot confirm one: an agent may do the finding, but deciding what your numbers mean is yours.
You manage the list in **Settings → Workspace → Brand family**:
* Proposals sit at the top, marked **Proposed by an agent**, with the evidence and a confirm or reject control for each.
* Confirmed properties are listed below them. With none, the group shows your domain and its subdomains, which already count.
* **Add a property** takes a domain, or a name with its kind.
Proposals are also announced in the [Inbox](/agent/inbox) as one row, "Are these yours?", listing each with its evidence. You confirm or reject them here, in Settings. Settings has no control to remove a confirmed property.
## Confirming a property changes history
An answer that named "Acme Analytics" last month named you, once you confirm that Acme Analytics is yours. So confirming a property re-reads your stored answers against the new family. It is the same re-extraction a competitor change goes through. A queued job does it within minutes, and the nightly reconcile catches anything left over. Rejecting a proposal, or adding a new one, changes nothing that was measured, so it re-reads nothing.
Until an answer has been re-read, it is counted as stored in the rolled-up numbers and left out of the per-property split. The split says how many answers are waiting, and why.
## Agents propose properties
Once a week, alongside the [markets proposals](/tracking/markets#agents-propose-markets), an agent looks for what else is yours:
* **Subdomains of your domain** seen in citations over the last 90 days, or in the crawl of your site. They already count; proposing one is asking you to name it.
* **Cited domains whose name carries your brand**, such as `examplehq.com` for `example.com`.
* **Domains your home page links to** in its structured data (`sameAs`) or its footer. Profiles on platforms such as LinkedIn, GitHub or G2 are left out.
* **Products** your Knowledge profile lists under what you sell, when they read as names rather than descriptions.
A domain candidate is also asked a "same organisation?" question before it is proposed. That check can only stop a proposal; it can never confirm one. A tracked competitor's domain is never proposed, and neither is anything you have already confirmed, rejected or listed. One pass proposes at most ten properties.
## How much of you each part is
Your headline numbers already include every confirmed property. The **Brand family** panel on **Sources** shows how much each part carries. It appears once a workspace has at least one confirmed property.
* **Rolled up**: how often answers named your brand or any property, and how often they cited a page of any of them, over every answer read, with the most-cited pages.
* **Per part**: your main brand first, then each confirmed property. Each has a **hit rate**, the answers in which it counted as you, named or cited. It is one rate because the stored answer records that the part counted, not which of the two it was. Each also has a **cited rate**, the answers citing a page on its domain (unmeasured for a part with no domain of its own), and its `n` and most-cited pages.
**The rows overlap.** One answer can name your main brand and cite your blog, so it counts on both rows. The per-part rows do not add up to the rolled-up line, and are not meant to.
`query_evidence` splits share of voice by `property`, and narrows it with `property_ids`. Only share of voice can be split this way, for the same reason as the hit rate.
## Where agents publish
The rule: if your main brand is `example.com`, then `blog.example.com` is much better than `blog.hello.com`. Agents rank every place a page could go in this order, and say why:
1. **A path on your domain**, such as `example.com/blog/`. It inherits everything the domain has earned, and every citation of it counts as you.
2. **A subdomain of your domain**, such as `blog.example.com`. It carries your brand and counts as you, but search engines treat a subdomain as a partly separate site, so it starts behind a path.
3. **Another domain you have confirmed**, such as `hello.com`. It counts as you, but it builds its authority alone and splits your brand across two sites.
4. **A new domain. Agents never propose one.** It starts with no authority and splits your brand, and starting a new site is your decision.
Agents have this ranking twice: in the knowledge brief they start from, and in a `brand_family` tool they ask before choosing where a draft, a new page or a tool will live. Given the place a draft is meant for, the tool also checks it: anywhere but a path on your domain is flagged unless the draft states why the better option does not fit, and a domain you have not confirmed is flagged whatever reason it gives. A proposed property is listed to the agent as proposed, so it is never treated as yours before you confirm it.
## Over MCP and the API
[`get_brand_family`](/mcp/tools#get_brand_family) returns every property with its status and evidence, then the window rolled up and split by part. It is MCP-only for now. Confirming, rejecting and adding properties is done by a person, signed in to the dashboard. An API key or an assistant connection cannot change the list.
Client reports carry a **Brand family** section once the workspace has a confirmed property. See [Client reports](/agencies/client-reports).
## Related
* [Markets](/tracking/markets): reading your prompts in more than one country.
* [Citations and sources](/measure/citations-and-sources): what counts as a citation of you.
* [Competitors](/tracking/competitors): the brands you are measured against.
# Competitors
Source: https://docs.attensira.com/tracking/competitors
Track rival domains alongside your own, and read competitor rates correctly — they do not sum to 100%.
## What does adding a competitor do?
Adding a competitor tells Attensira to look for that brand in the answers it is already collecting. No extra runs are issued and no extra prompts are consumed. Every answer from every tracked prompt is re-read for the competitor's name and domain, and the result becomes a competitor share of voice and citation rate next to your own.
Because the analysis runs over answers you already have, a competitor added today is measured against answers collected from that point forward, on the same daily cadence as everything else. Competitors do not consume prompt slots.
Add a competitor by domain — `example.com` — rather than by display name. The domain is what makes a citation attributable; the brand name is what makes a mention detectable. Attensira uses both.
You can add one programmatically with the `add_competitor` MCP tool. See [MCP overview](/mcp/overview).
## How are competitors suggested at signup?
At signup you choose your competitors from suggestions instead of typing a list. While the agents set up your workspace, they store up to ten rivals named on your site and in its analysis as **candidates**. A candidate is not a competitor yet: no share of voice, rival table, rollup or agent tool includes one. The only thing that reads candidates is the reading of answers, so the step can say how often each one was named before you choose.
The suggestions on the competitors step come from three places, in this order:
1. **Brands the free scan's answers named**, with the count: "Named in 11 of 150 answers".
2. **Domains those answers cited beside yours.**
3. **Candidates no answer has named yet**, from your site, ranked last.
The scan is usually still reading while you look at this step. Counts say "so far", and the step says how many answers are still to come, so an early count is not read as final.
Every suggestion starts ticked. Untick the ones you do not lose deals to, and add any that are missing by their website.
### The one question
The agents check each candidate against your business profile: is it in the same market as you? When they are confident, nothing is asked. When they are unsure, they ask you about exactly one candidate, inline: track it, or leave it out. That candidate starts unticked, because ticking it would be answering for you. The check never tracks a rival on its own. Only your choice does.
### When you continue
* The ticked suggestions, and the ones you added, become tracked competitors.
* The rest are removed, and never reach a share figure.
* The scan's answers are read again against the competitors you chose, so your first numbers use your list.
* If the question was still unanswered, it goes to your [Inbox](/agent/inbox) as a decision, once per domain, and that candidate is kept until you answer.
## What gets measured for a competitor?
The same two rates you get for yourself, over the same runs:
* **Share of voice** — successful runs that name the competitor, divided by all successful runs.
* **Citation rate** — successful runs that attach a URL on the competitor's domain, divided by all successful runs.
A **mention** is the brand appearing in the answer text with no link attached. A **citation** is one URL the model attached to the answer. These are different events and a strong brand often has one without the other: models name well-known companies from memory and cite whichever page they happened to retrieve.
Alongside these, **source share** tells you what fraction of all citations in the window went to a given domain — a competitor's domain included. And **AvgPosition** is the mean index of a URL within the model's citation list, computed **per source domain**. It is not a brand rank and it is not a leaderboard position; a low AvgPosition means that domain's links tend to appear early in the list of links, nothing more.
Attensira does not rank brands against each other. Your **position** counts where you came among the names you track in one answer, and **sentiment** scores how answers that take a position talk about **you**; the Sentiment page does not score competitors. Each answer's full [brand order](/measure/share-of-voice#the-full-brand-order) lists every brand it named, competitors included, as evidence for that one answer, not as a leaderboard.
## Why don't competitor rates add up to 100%?
Because a single answer can name several brands at once, and every rate is measured against the same denominator: **all successful runs in the window**. Each brand is scored independently — "was this brand named in this answer, yes or no" — so the rates overlap by design and routinely total well over 100%. They can also total under 100%, when many answers name nobody you track.
Every competitive product you have used implies the opposite. Share of voice, market share, ranking positions — these all divide a fixed pie and sum to one. Attensira does not, because an assistant answering "what are the best expense tools for a small agency?" does not pick a winner. It lists five.
### A worked example
Ten successful runs of one prompt. Here is what the answers contained:
```text theme={null}
Run 1: names You, Acme, Borealis
Run 2: names Acme
Run 3: names You, Acme
Run 4: names Borealis
Run 5: names You, Acme, Borealis
Run 6: names Acme, Borealis
Run 7: names nobody tracked
Run 8: names You
Run 9: names Acme, Borealis
Run 10: names You, Acme
```
The rates:
| Brand | Runs naming it | Share of voice | Denominator |
| - | - | - | - |
| You | 5 | 50% | 10 successful runs |
| Acme | 7 | 70% | 10 successful runs |
| Borealis | 5 | 50% | 10 successful runs |
That totals 170%. Nothing is wrong. Acme appears in seven of ten answers; you appear in five; they appear together five times. Run 7 named nobody, and it still sits in every denominator — an answer that ignored the whole category is real evidence, not a missing data point.
The practical consequence: **your share of voice going up does not require a competitor's to go down.** You can both rise, if models start naming more products per answer. Read each rate as "how often is this brand in the room," never as "what slice of the room does this brand own."
The `share_of_voice` field holds the rate defined above, on the denominator defined above. It is not a slice of a total and it does not sum to one across brands.
## How do I compare a competitor fairly?
Compare like against like. Both rates come from the same runs, the same prompts, the same model surfaces and the same days, so a difference between two brands on one topic is a real difference. A difference between two topics is not, because the prompts differ.
Watch the counts. Rates arrive as `{value, n}`, and `n` is the number of successful runs behind the number. A competitor at 100% off three runs and one at 62% off eighty are not comparable claims. A `value` of `null` means not measured — no successful run — while `0` with a non-zero `n` is a real, measured absence.
Changes carry `{value, real}`. Attensira applies a two-proportion z-test at 95% before it reports a movement. Below that noise floor you get `{real: false, value: null}`, which means "no change we can prove." Do not read it as flat, and do not put it in a board deck as zero.
Finally, remember what a competitor rate is not: it is not traffic, not revenue, and not a ranking. It is the frequency with which a model, asked your buyers' questions, brings a brand up. See [Models](/tracking/models) for how the surfaces you track shape that number.
# Markets
Source: https://docs.attensira.com/tracking/markets
Read your prompts in more than one country, and in the language buyers there ask in. Each engine says how it was located there, an engine that cannot be is shown as not measured, and the cost is shown before you save.
## What is a market?
A **market** is a country, and optionally the language a prompt is asked in there. Every workspace has a **primary market**: the country it was set up with. With no other markets, nothing changes: each prompt × model is read once a day from that country, exactly as it always was.
Add a second market and every prompt is read again from that country, on every model. The unit of work becomes **prompt × model × market**, and each market has its own rates, its own `n` and its own trend. A workspace, a topic or a prompt can hold up to **five** markets.
Markets cost [AI responses](#what-markets-cost): a second market over every prompt doubles them. Settings prices every change before you save it, and agents propose a second market [scoped to the topics with demand there](#agents-propose-markets) rather than to everything.
## Where are markets set?
**Settings → Workspace → Markets and engines** holds the workspace's list:
* **Primary market** comes first. Every number defaults to it, and when the response allowance runs short, extra markets pause before it does. To change it, make another market in the list the primary.
* **Markets** is the ordered list. Each market can carry a language.
* **Engines** are the models read in every market above. At least one has to read.
* Under each market, one line says how each engine is read there: "Read locally on every engine", or the engines that are not, such as "Claude: location stated in the question only".
Nothing is saved until you save, and saving shows the [cost preview](#what-markets-cost) first.
### Topic and prompt overrides
A topic or a prompt can read in markets of its own:
* **A topic**: open the topic's menu and choose **Markets**.
* **A prompt**: open its move dialog and switch to **Prompt markets**.
Each offers **inherit** or a list of its own, and each change goes through the same cost preview. The most specific setting wins:
```
prompt markets → prompt country → topic markets → topic country → workspace markets → workspace country
```
An empty list goes back to inheriting, so clearing a list can never leave a prompt read nowhere. The first market of whichever list applies is that prompt's primary market.
### Asking in a language
A market with no language asks the prompt **as written**. A market with a language, such as Germany with `de`, asks it **in German**: a buyer in Germany asks ChatGPT in German, and the brands and pages that answer cites are not the answer to the English question.
* An agent translates each prompt once per language. Your brand, your domain and your tracked competitors' names are kept exactly as written. A prompt is translated again only when its text changes.
* If the translation cannot be made, that market's readings **wait** and are retried on the next pass. They are never asked in the original language under the German market's name, which would be a different question with the wrong label. Loop health records it as `translation_failed`.
* Languages come from a fixed list of BCP-47 tags. A tag outside it is refused when you save, rather than becoming a market that never reads.
`ar`, `bg`, `cs`, `da`, `de`, `de-AT`, `de-CH`, `el`, `en`, `en-AU`, `en-CA`, `en-GB`, `en-IE`, `en-IN`, `en-NZ`, `en-US`, `es`, `es-419`, `es-ES`, `es-MX`, `et`, `fi`, `fil`, `fr`, `fr-BE`, `fr-CA`, `fr-CH`, `fr-FR`, `he`, `hi`, `hr`, `hu`, `id`, `it`, `ja`, `ko`, `lt`, `lv`, `ms`, `nb`, `nl`, `nl-BE`, `pl`, `pt`, `pt-BR`, `pt-PT`, `ro`, `ru`, `sk`, `sl`, `sr`, `sv`, `th`, `tr`, `uk`, `vi`, `zh-CN`, `zh-Hans`, `zh-Hant`, `zh-TW`
**One language per country, for now.** A prompt × model × country is read once a day, so a country listed twice in two languages (Switzerland in `de` and in `fr`) is read in the language listed first. The other is recorded as [not measured](#not-measured-is-not-zero), with the reason "this country is already read in another language for these prompts". The two are never pooled into one number.
## How is each model located in a market?
Not every model can be asked from a country. Attensira writes down how each one was, and never presents one kind as another.
| Model | Read through | In a market | Mode |
| - | - | - | - |
| ChatGPT, Perplexity, Google AI, Gemini | The consumer product, from a browser in that country | Read as a person there sees it, in the [countries listed below](#which-countries-can-be-read-locally) | `geo_proxy` |
| Claude | Its API | The location is **stated in the question**. It is not a local search | `hinted` |
| Any model read through its API fallback | Its API | The location is stated in the question | `hinted` |
| A consumer model in a country outside the list | — | **Not read.** No call is made and nothing is counted | `unsupported` |
**Claude is never read from a location.** There is no consumer reader for Claude. Its answers come from the API, and the country is added to the end of the question as a line reading `User location: DE`. That has always been how Claude is asked in your primary market as well. A Claude rate in Germany tells you how Claude answers someone who **says** they are in Germany. It does not tell you what someone searching from Germany sees, so it is labelled "location stated in the question, not a local search" everywhere it appears.
Every reading in a secondary market, and every reading asked in a market language, records its language and its mode, so later comparisons can set like against like. Readings in the primary market asked as written are left exactly as they were, with nothing new recorded on them.
### Which countries can be read locally?
The consumer models can be read from a browser in these 42 countries:
United Arab Emirates (AE), Argentina (AR), Austria (AT), Australia (AU), Belgium (BE), Brazil (BR), Canada (CA), Switzerland (CH), Chile (CL), Colombia (CO), Czechia (CZ), Germany (DE), Denmark (DK), Spain (ES), Finland (FI), France (FR), United Kingdom (GB), Greece (GR), Hong Kong (HK), Ireland (IE), Israel (IL), India (IN), Italy (IT), Japan (JP), South Korea (KR), Mexico (MX), Malaysia (MY), Netherlands (NL), Norway (NO), New Zealand (NZ), Philippines (PH), Poland (PL), Portugal (PT), Romania (RO), Saudi Arabia (SA), Sweden (SE), Singapore (SG), Thailand (TH), Turkey (TR), Taiwan (TW), United States (US), South Africa (ZA).
The list is deliberately conservative. A country that cannot be confirmed is treated as unsupported, because the alternative is an answer read from somewhere else and labelled as that country's.
**A primary market outside the list** is still read on every model, exactly as before, but the consumer models' answers there may not be local. [`get_markets`](/mcp/tools#get_markets) says so in that cell's `note`: "Read as before, but ChatGPT cannot be read from KE, so its answers may not be local." Such a market is never compared with another on those models, because how it was located cannot be stated.
## Not measured is not zero
A secondary market that a model cannot be read from is a **gap**: no call is made, no reading is written, and the gap is recorded with its reason. It is shown as "Not measured in Kenya on ChatGPT", with the reason, never as 0% and never folded into another market's number.
| Reason | What it means |
| - | - |
| `market_unsupported` | That model cannot be read from that country. For the consumer models, the country is outside the [list above](#which-countries-can-be-read-locally) |
| `language_shares_country` | The country is already read in another language for these prompts. See [one language per country](#asking-in-a-language) |
Gaps appear on the Markets section of the analytics overview, in `get_markets`, in `query_evidence` answers about markets, and in [client reports](/agencies/client-reports).
A secondary market that was skipped because the month's responses ran short is not a gap. Its readings are recorded as unmeasured, and the [allowance section](#the-primary-market-is-read-first) explains what happens.
## Comparing markets
The **Markets** section of the analytics overview shows one row per market: its share of voice with `n`, its citation rate, how many prompts were read there, and its change against **the same market** in the window before. One market is a sentence, not a table. Two or more markets read under this setting add a split by model and surface, each with its mode, and a comparison of every market against every other on each model.
Attensira says one market leads or trails another, for example "DE trails US by 12 points (n = 84 vs 340)", only when all three of these hold:
1. **Both were read the same way** on that model and surface. A market read from a local browser is never compared with one whose location was stated in the question, because the comparison would be of the method, not the place. The row says "not compared: DE and US are not read the same way". Claude in two markets is stated-in-the-question on both sides, so it can be compared. Read that comparison as a difference in how Claude answers a stated location.
2. **Each side has at least 30 successful answers** on that model and surface in the window.
3. **The gap clears the same significance test** as every other delta: a two-proportion z-test at 95%. See [How we collect data](/measure/how-we-collect-data#what-is-the-significance-gate).
Below the floor the row says "no clear difference yet (n = 12 vs 340)". Over the floor but inside the noise it says "no clear difference (n = 45 vs 340)". Neither carries a number of points, because a number is how a screen says "this is a difference".
| Claim | What it needs | Below that |
| - | - | - |
| A market's rate | Nothing. It is always shown with its `n` | The rate and its `n` |
| A market's own change | The significance gate: at least 2 runs a side, z-test at 95% | "No proven change" |
| One market against another | Same mode, at least 30 answers a side, and the gate | "No clear difference yet", with both `n` |
On Starter, a market read on a few prompts collects only a few answers a week per model. The cost preview says when that is the case: "weekly comparisons need about 4 weeks here; monthly comparisons are available sooner". Widen the window rather than reading a week.
**Adding a market does not show as a drop.** A headline number pooled over markets would fall the day a weaker market starts being read. When the set of markets differs between the two windows, the change is computed over the markets read in both, and the chart carries a marker such as "Germany added".
To see everything for one market, cut the analytics to it: choose the market in the Markets section, and an **Asked from Germany** chip keeps the cut visible until you clear it. The prompts board can also be grouped **by market**, with one section per market. A prompt read in two markets appears in both.
## What markets cost
Readings are metered in **AI responses** against your plan's monthly response allowance, not in credits. See [Plans](/account/plans#the-monthly-response-budget-is-a-flow).
```
responses a month = prompts × models × markets × samples × 30
```
Allowances are sized for **one** market at full use. Starter is 50 prompts × 4 models × 1 sample × 30 days = 6,000 responses a month. A second market over every prompt makes that 12,000, which is 6,000 over the allowance. The same market over the 10 prompts that have demand there adds 1,200.
### The cost preview
Every change to markets or engines is priced before it is saved, at the workspace, topic and prompt level. The preview shows:
* **Responses a month, now and after**, across every workspace in the organisation, because the allowance is the organisation's.
* **What your plan includes**, and how far over it the change goes.
* **What happens past the limit**, in the words of your overage policy: readings in the extra markets pause, or they continue and the extra responses are billed as overage.
* **Answers a week**: when a market would collect fewer than 30 a week on a model, the note above, with the lowest count.
* **What will not be read**: the market × model cells that cannot be located, "never counted as zero".
A change that raises your responses past the allowance is saved only when you confirm it. Without that confirmation the save is refused with `409 over_allowance`, carrying the same preview, so an old tab or a stale approval cannot save past the allowance unseen. A change that lowers your responses is never refused.
### The primary market is read first
A second market can never stop your primary market's readings.
* The primary market's readings reserve their responses first, exactly as they did with one market.
* Each secondary market reserves its own responses separately. If that is refused, only that market is skipped.
* Under the **pause** policy, a secondary market may only spend the part of the allowance your primary markets are not projected to need for the rest of the month. Late in the month, that headroom runs out before the allowance does.
* Under the **bill** policy, secondary markets keep reading and the extra responses are billed as overage.
A skipped market is never silent. [Loop health](/agent/loop-health) records the day's skip as `market_allowance_reached`, and the [Inbox](/agent/inbox) gets one row per market per month: "Readings in DE are paused until the allowance resets". To read it sooner, track it on fewer topics, or move to a plan with more responses.
## Agents propose markets
You do not have to find your markets yourself. Each workspace is looked at once a week, and the morning after you connect Search Console. An agent gathers the evidence and needs at least one strong signal:
| Evidence | Strength |
| - | - |
| Search Console: the country's share of your Google impressions over 90 days, and the queries behind it | Strong from 5% and 100 impressions |
| Knowledge: the markets you say you sell into | Strong |
| Your site: an hreflang version for the country, or a country domain that names it | Strong |
| Your home page's declared language | Weak |
| Countries you already set on some prompts or topics | Weak |
The best candidates become proposals, at most three standing at a time. Each is **scoped to the topics with Search Console demand in that country**, at least 20 impressions on a topic's queries. Only when there is no per-topic demand does a proposal cover every topic, and it says why. A proposal asks in the country's main language when that language is on your Knowledge **Languages you publish in** list, and otherwise asks as written and says so.
Each proposal arrives in the Inbox as one row, "Track Germany too?", with the evidence, the topics it covers and the cost from the same preview Settings shows. To take it, set the market on those topics from each topic's menu (**Markets**), which shows the same cost preview before it saves. **Dismissing the row**, or marking it done without taking it, declines it: nothing changes, and that country is not proposed again for 90 days. A row you snooze stays open.
Agents never save markets themselves. A workspace with no country set gets no market proposals, because adding a market beside a primary that does not exist would change what every existing reading means.
## Over MCP and the API
* [`get_markets`](/mcp/tools#get_markets) returns the markets, engines and how each engine is read in each market, then the window by market with comparisons and gaps.
* [`preview_markets`](/mcp/tools#preview_markets) prices a change in responses and saves nothing.
* `query_evidence` groups by `country` and filters on `markets`, and answers with the gaps as gaps.
These three are MCP-only for now. Saving markets is done by a person, signed in to the dashboard. An API key or an assistant connection cannot change them.
## Related
* [Brand family](/tracking/brand-family): the other domains and names that count as you.
* [Models](/tracking/models): which surfaces have readers at all.
* [How we collect data](/measure/how-we-collect-data): the daily cadence, sampling depth and the significance gate.
# Models
Source: https://docs.attensira.com/tracking/models
Choose which assistant surfaces answer your prompts, and understand tracked versus readable.
## What is a model in Attensira?
A model is an assistant surface that answers your prompts — ChatGPT, Perplexity, Claude, Gemini, Google AI. In code and on the wire it is called a **channel**, and the channel id (`chatgpt`, `perplexity`, `google_ai`, `claude`, `gemini`) is deliberately stable. When a vendor swaps the underlying model behind their product, the channel id does not change, so your history stays continuous instead of splitting into a new series every release.
That stability is the point of the concept. You are tracking the surface a buyer actually uses, not a specific model version they will never see the name of.
This is not the same thing as an **agent model** — the LLM that a chat inside Attensira runs on, which Attensira picks for each turn. Agent models power the assistant you talk to. Model channels are the surfaces Attensira measures.
## Which models does a new workspace track?
A new workspace starts on **four**: ChatGPT, Perplexity, Claude and Google AI. That is the default channel set, and it is a starting configuration rather than a limit. Gemini, on Growth and above, is switched on by hand.
Attensira does not offer five models to everyone. The readable set is defined in the engine, and it is not the same as the list of assistants that exist in the world:
| Channel | Wire id | API reader | Consumer reading |
| - | - | - | - |
| ChatGPT | `chatgpt` | Yes | Yes |
| Perplexity | `perplexity` | Yes | Yes |
| Claude | `claude` | Yes | No |
| Gemini | `gemini` | Yes | Yes |
| Google AI | `google_ai` | **No API reader** | Yes |
Google AI has no API reader. Whatever coverage exists for it comes from consumer reading only, and you should expect thinner, less regular data there than on the API-backed channels. Say this out loud in any report you build on it.
## Are models gated by my plan?
Partly. Starter reads four channels — `chatgpt`, `perplexity`, `claude` and `google_ai`. Growth, Business and Agency add `gemini`, which is the only plan-gated surface. Switching Gemini on in a Starter workspace changes the setting but not the readings: the scheduler queues only the engines the plan includes, so nothing is asked and the metric comes back untracked rather than as a zero. Your plan also governs [prompt slots](/tracking/prompts) and sampling depth. See [Plans](/account/plans).
Within your plan's channels, the allocation is yours.
The real constraint is the **response budget**. Asking more models multiplies the work: every model you add multiplies your prompt count by one more daily reading per prompt, per country, at your plan's sampling depth. Five channels across 50 prompts at n=3 is a very different volume of readings than two channels at n=1 — and the budget is spent proportionally. Each extra [market](/tracking/markets) multiplies it again, and the engines are chosen in the same Settings group as the markets, with the cost shown before you save.
You choose the allocation. Two common shapes:
* **Wide and shallow.** Track four or five channels across a smaller set of prompts. Good when you do not yet know where your buyers are and want to find out.
* **Narrow and deep.** Track two channels across many prompts. Good when you already know one surface drives your pipeline and you want statistically usable rates on a wide question set.
Credits and the response budget are currently inconsistent in the product — a known open bug we are fixing. Because of that, this page does not publish a table of how many readings a plan's grant buys, and we make no promise that monitoring is free of credits. See [Credits](/account/credits) for what a credit is and what spends it.
## What do "tracked" and "readable" mean?
Every per-model metric Attensira returns carries two flags, `{tracked, readable}`, and reading them wrong is the most common way to misreport a result.
**`tracked: false`** means the channel is not configured for this workspace. It was never queried. There is no answer, because no question was asked. Rendering that as 0% invents a failure that did not happen — it says a model declined to name you, when in fact you never asked it.
**`readable: false`** means no reader exists for that surface at all. This is a capability of the platform, not a setting in your workspace, and no amount of configuration turns it on. `google_ai` over the API is the live example.
Both are distinct from a measured zero. Put together with the number shapes:
| What you see | What it means |
| - | - |
| `tracked: false` | Not configured here; never queried. Not a zero. |
| `readable: false` | No reader exists for this surface. Not a zero. |
| `{value: null, n: 0}` | Tracked, but no successful run in the window. Not measured. |
| `{value: 0, n: 12}` | Asked twelve times, named zero times. A real zero. |
This bites hardest in per-model breakdowns and in exports, where an untracked channel and a channel that never named you look identical unless you check the flags. If you are building a dashboard on `get_analytics`, branch on `tracked` and `readable` before you format a percentage. See [MCP overview](/mcp/overview).
## How do I change which models I track?
Change the channel set in your workspace settings. A newly added channel starts producing data from its next scheduled run and does not backfill, so its history begins the day you add it, exactly like a new prompt.
Each prompt × model × country combination runs at most once per workspace-local calendar day, at your plan's sampling depth — n=1 on Starter, n=3 on Growth, Business and Agency, issued concurrently. Failed runs are stored but excluded from `n`, so a channel having a bad day shows up as a smaller `n`, not as a drop in your rate.
Turning a channel off stops future runs. It does not delete the readings already collected, which remain subject to your plan's retention window. Turning it back on later resumes collection but leaves a gap in the series for the period it was off — and during that gap the metric is `tracked: false`, not zero.
Before adding a channel across the board, add it and wait two weeks. Rates on a new surface are noisy until `n` builds, and the two-proportion z-test at 95% that guards deltas will correctly refuse to call a change until it has the evidence.
## A model is not a crawler
Models and AI crawlers are different systems and Attensira reports them separately. A model answers a prompt. A crawler fetches pages from your site. A high crawl rate does not produce mentions, and a model can name you having never crawled you.
The two also fail differently, and crawler traffic cannot be measured the way web analytics measures humans: AI crawlers do not execute JavaScript. A client-side beacon, however it is written, has never seen one and never will — by the time the script runs, the only visitors left are humans with browsers. See [AI traffic](/traffic/ai-traffic).
# Prompts
Source: https://docs.attensira.com/tracking/prompts
Add the questions people actually ask assistants, and manage the prompt slots your plan gives you.
## What is a prompt?
A prompt is a question a person actually types into an assistant, written the way they would type it. "What's the best expense tool for a 20-person agency?" is a prompt. "expense tool agency" is a keyword, and no one asks it. Attensira runs each prompt against the model surfaces you track and reads the answer that comes back.
The distinction matters because the answer is prose, not a ranked list. A model replies with a paragraph or two naming a handful of products, sometimes with links attached. What Attensira measures from that answer is whether your brand was named (a **mention**), whether the model attached a URL on your domain (a **citation**), and which domains it drew on (**sources**). Keyword-shaped input produces a thin, unnatural answer that tells you nothing about how buyers are actually being advised.
In code and in the API, a prompt is a `keyword_id`. The name is historical; the object is a prompt.
Write prompts in the words your buyers use, not your own category language. If your customers say "invoice chasing" and your site says "accounts receivable automation," track both and compare.
## How do I add prompts?
You paste the questions; the agent files each one under a topic; you confirm or correct it before anything is tracked.
Every prompt belongs to one workspace. A workspace is one brand and one set of prompts, competitors, and model surfaces. The dialog opens with the paste box on one side and the [suggestions panel](#where-do-suggestions-come-from-in-the-add-dialog) on the other.
Phrase each as a complete question. Avoid your brand name in the prompt itself unless you specifically want to measure branded questions — an unbranded prompt is the honest test of whether a model reaches for you unprompted.
A paste out of a spreadsheet, a document or a chat works as it is: list markers (`-`, `*`, `•`, `1.`, `2)`), wrapping quotes and trailing commas or semicolons come off, and a line repeated in the paste counts once. A line over 500 characters is flagged, and Review waits until it is shortened, because the whole add would otherwise be refused for it.
Every line is listed with the topic the agent would file it under: one of your topics, or a new one it names. Each topic is a select you can change: another topic, **New topic…** to start one of your own, or **No topic**. New topics are named once, above the table, and a rename there reaches every line filed under that topic. A topic name is at most 60 characters.
The button says how many prompts it adds. New topics are created first, then every prompt is added in one write, each under its topic. Each new prompt takes one slot.
No question in the paste disappears from the review. A line the agent did not place stays in the table with its reason, and you choose a topic for it or leave it without one. The reasons you will see most often:
| Reason shown | What it means |
| - | - |
| Not sure where this goes | The agent was not confident enough to pick a topic, so it does not guess. |
| Longer than 300 characters | Too long for the agent to file. You can still choose a topic for it yourself. |
| Already tracked | The workspace already tracks this question. It is shown as **Unchanged** and is not added again. |
| Topic suggestions are unavailable | The agent could not be reached. See below. |
A new topic the agent names that matches one of your topics, ignoring case, is filed under the topic you already have rather than creating a second one with the same name.
**The agent being unavailable never blocks the add.** When it cannot be reached, or your credit balance is at zero, the review opens with "Topic suggestions are unavailable right now. Choose a topic for each line, or add them without one." Every line starts as **Choose a topic**, and a line you leave unchosen is added without a topic.
**A topic that fails to create adds nothing.** If one of the new topics is not created, no prompt is added: the review names the topics that were created and the one that failed, and the button becomes **Retry**, which carries on from the topic that failed without creating the earlier ones twice.
Adding from a topic's own page (**Add prompt** in the topic's menu) skips the proposal: every line is filed under that topic.
You can also add prompts programmatically with the `add_prompts` MCP tool. That tool costs credits. See [MCP overview](/mcp/overview).
```json theme={null}
{
"prompts": [
"What is the best expense tool for a 20-person agency?",
"How do small teams handle receipt capture without a finance hire?"
]
}
```
New prompts do not backfill. A prompt starts producing data from its first scheduled run onward, so the history you see for it begins the day you added it.
## When does a prompt actually run?
An hourly ticker picks up scheduled work, but each prompt × model × country combination runs **at most once per workspace-local calendar day**. Adding a prompt at 09:00 and again pressing save at 11:00 does not get you two readings. The daily ceiling is the unit of measurement.
How many readings that day produces depends on sampling depth: **n=1 on Starter**, **n=3 on Growth, Business and Agency**, issued concurrently. Deeper sampling is why the same prompt on Growth gives a steadier rate than on Starter — one reading can only ever be 0% or 100%.
Runs that fail are stored but excluded from `n`. A failed run is not a zero; it is an absence, and the metrics exclude it rather than counting it against you.
## How do prompt slots work?
A prompt slot is a **stock, not a flow**. A slot is occupied for as long as a prompt is tracked, and it returns to your pool the moment you remove that prompt. You are not buying a monthly allowance of prompts you can burn through — you are buying how many prompts can be under observation at once.
| Plan | Price / month | Prompt slots | Sampling depth | Retention |
| - | - | - | - | - |
| Starter | \$129 | 50 | n=1 | 3 months |
| Growth | \$349 | 150 | n=3 | 12 months |
| Business | \$699 | 350 | n=3 | Unlimited |
| Agency | \$2,000 | 750 | n=3 | Unlimited |
Yearly billing is charged at ten months' price. There is no Pro plan.
So a Starter workspace that adds 50 prompts, removes 10 in March and adds 10 different ones in April is still within its 50 slots. Nothing accumulates and nothing expires.
Removing a prompt frees its slot immediately, but the readings already collected stay subject to your plan's retention window. Removing a prompt does not delete its history, and re-adding the same text later starts a fresh prompt rather than resuming the old one.
### The slots counter
The Prompts screen's header and the add dialog both show how many slots are in use, for example "142 of 150 prompts on your plan", and the two always agree: an add moves both at once.
A paste larger than the slots you have left is stopped before anything is sent, with how many would fit: "3 of these 10 fit: your plan has 3 prompt slots left. Remove 7 lines or upgrade." If the plan's limit is reached between the count and the add, nothing is added and the dialog says how many slots the plan has left. When the count cannot be read, the counter says "Prompt slots unknown" with a Retry, and nothing is refused on a guess; the add itself still checks the limit.
## Does Attensira find new prompts for me?
Once a week, it looks for buyer questions you are not tracking yet. Two signals: URLs your competitors get cited on inside your existing readings, and pages AI crawlers (GPTBot, ClaudeBot, PerplexityBot and others) fetched from your site in the last 14 days with no tracked prompt behind them. Page titles and headings are matched against a small set of buyer-question shapes ("best X for Y", "how do I Z", "X vs Y", "top N for Z"), and anything too close to a prompt you already track is dropped.
Discovery never adds a tracked prompt on its own. At most 3 candidates a week land as one Inbox row, "Track these questions?", each with its evidence (the competitor URL, or the crawler hit count and an example URL) and, where known, how many prompt slots your plan has left. Approving adds the prompt the same way you would by hand. Nothing is tracked until you say so.
### Where do suggestions come from in the add dialog?
The add dialog has a collapsible **Suggestions** panel beside the paste box, drawn from real Search Console queries, from the sub-questions assistants work through on prompts you are losing, and, on a new workspace, from a starting guess. Each row shows its volume (Search Console impressions, or "Volume —" when none has been measured, which is not a zero) and the reason it was suggested.
* **Track** adds one suggestion at once, under the topic it was suggested for if it has one.
* **Dismiss** (the ✕) removes it, and it is not suggested again.
* Tick several and press **Track these** to put them in the paste box. They then go through the same topic proposal as anything you typed, and a suggestion is marked tracked only once its prompt is actually added.
## What is each prompt filed under?
Every tracked prompt carries a few attributes beside its topic. Every one is optional, and a missing one is shown as —, never as a default: a prompt with no intent is not "informational".
Today you read them through `query_evidence` over [MCP](/mcp/tools), which can group or filter any metric by intent, stage and persona. The dashboard does not show or edit them yet, so for now the agent's filing stands and tags cannot be added.
| Attribute | Values | Who sets it |
| - | - | - |
| **Intent** | `informational`, `comparison`, `commercial`, `transactional` | The agent, until a person sets it |
| **Stage** | `awareness`, `consideration`, `decision`, `retention` | The agent, until a person sets it |
| **Persona** | One of the buyer roles you confirmed in your brand profile | The agent, until a person sets it |
| **Priority** | `high`, `medium`, `low`, or — | Computed when it is read, never stored |
| **Origin** | `user`, `search_console`, `fanout`, `competitor`, `discovery`, `seed`, `agent` | Set once, when the prompt is added |
| **Tags** | Your own labels | A person only |
### Intent and stage
**Intent** is what the person asking wants from the answer:
* `informational`: to understand something, with no product choice on the table ("how do I close the books at month end").
* `comparison`: to weigh named products against each other, or find alternatives to one ("Xero vs QuickBooks").
* `commercial`: to research what to buy, without a name in mind ("best invoicing tool for freelancers").
* `transactional`: to act on a named product: its price, its plans, a sign-up, a trial.
**Stage** is where in the buying journey it is asked: `awareness` (understanding the problem), `consideration` (looking at kinds of solution and lists of products), `decision` (choosing between named products) or `retention` (already a customer: using, connecting, fixing or leaving a product).
### Persona
A persona is one of the roles in the **Who buys** field of your [brand profile](/agent/knowledge-and-skills#company), one role per line or per semicolon. Only a field someone confirmed counts: a value Attensira drafted from your site is our guess, so no prompt is filed under it. With no confirmed roles, no prompt has a persona. Confirming or changing the roles files your prompts again on the next night, except the ones a person set.
### How the agent files them
The agent files a prompt by intent, stage and persona within a minute or two of it being tracked, and a nightly pass picks up anything it missed. Each attribute is one question with a fixed list of allowed answers. An answer the agent is not confident in counts as no answer, and the attribute stays — rather than guessed. If the agent cannot be asked at all, the prompt is left as it was and asked about again on the next pass.
**A person's value is final.** Once a person sets a prompt's intent, stage or persona, the agent never files that prompt again, even when its own rules change.
### Priority
Priority is how much a prompt is worth working on first: its monthly search volume, weighted by how close its intent is to buying. On a log scale, a transactional prompt counts its whole volume, a commercial one 0.8 of it, a comparison 0.7 and an informational one 0.4. In practice a transactional prompt is **high** from about 250 searches a month and **medium** from about 15; an informational prompt needs about 1,000 searches a month to reach medium.
Priority is computed every time it is read and never stored, because the search volume behind it is refreshed and a stored priority would outlive the number it came from. It is — whenever the volume or the intent is not known. A measured volume of zero is a real zero and gives **low**; an unknown volume gives —. Priority is not shown in the dashboard yet, and the prompts board does not carry search volume, so for now every prompt's priority is —.
### Origin
Where the prompt came from, set when it is added and never changed: `user` for a person adding it (in the dashboard, over the API or over MCP, including `add_prompts`), `search_console` for an accepted suggestion from your own Search Console queries, `fanout` for a sub-question under a tracked prompt, `competitor` for a question taken from competitors' answers, `discovery` for a prompt Attensira proposed and you accepted, `seed` for a new workspace's starting set, and `agent` for a prompt an agent added during a run.
### Tags
Tags are yours alone; nothing files them. A tag is stored trimmed and in lower case, so "Q4 Launch" and "q4 launch" are the same tag. A tag is at most 40 characters, and a prompt holds at most 20.
## Is a prompt won or lost?
Each prompt has a **state** across the engines it is read on (won, contested, lost, absent, or — when nothing was measured), and each engine has a cell (named and linked, named, lost, absent, or —). Both are read from the latest successful answers in the window, with fixed thresholds. [Prompt state](/measure/prompt-state) gives the rules.
## How do I remove a prompt?
Delete it from the prompts view, or call the `remove_prompt` MCP tool. The slot is returned as soon as the removal is accepted, so you can add a replacement in the same sitting.
`remove_prompt` is marked as a destructive tool in the MCP schema. An agent holding your key can remove tracked prompts, so treat the key as a workspace-level credential — see [Authentication](/account/api-keys).
Remove prompts when the question stopped mattering, when two prompts turned out to be the same question in different words, or when a prompt is producing answers that never mention anyone in your category. Rotate slots deliberately rather than hoarding them: a tracked prompt you never read is a slot doing no work.
### Moving or deleting many prompts at once
Tick prompts in the grid and a bar appears above it: "12 selected", with **Move…**, **Delete…** and **Clear**, and **Ask** when the selection gives the agent something to work from. One change takes up to 200 prompts; over that, the bar says so before anything is sent.
* **Move…** files the selection under one topic, or takes it out of every topic. The readings move with the prompts, and so do the engines each is read on. A prompt without a market of its own takes the market of the topic it lands in. Moving changes topic numbers for past days; see [Topics](/tracking/topics#merging-splitting-and-dragging).
* **Delete…** lists every selected prompt and asks you to type the count before it runs, so 20 is never deleted when you meant 2. Each deleted prompt frees its slot.
* **Ask** hands the selection to the agent as a brief, with the question "What would move the most of these?"
A bulk move or delete is **not all-or-nothing**. Each prompt is moved or deleted on its own, and the note on the Prompts screen says what happened in those terms: how many landed, how many did not and why, and where the rest still are ("still in their old group", or "still tracked, with every reading intact"). Retry only what did not land.
## Reading the numbers on a prompt
Every rate is reported as a value plus the count it came from, `{value, n}`. A `value` of `null` means **not measured** — no successful run exists for that window. A `value` of `0` with `n` above zero is a real, measured zero: the model was asked and did not name you.
Changes over time carry `{value, real}`. Attensira runs a two-proportion z-test at 95% confidence, and a change that does not clear the noise floor is returned as `{real: false, value: null}` — "nothing we can prove yet," which is not the same as no change. [Reading the numbers](/measure/reading-the-numbers) covers all three rules.
## Where do you stand on a prompt?
A rate says how often you were named. It does not say who had the answer when you were not. "Named 0% of the time" reads the same whether a rival took every answer or nobody was named at all, and those are different jobs. **Standing** is the missing word: one state per model, and one for the prompt across its models.
Standing is built from the same readings as the rates beside it. It is shown in the dashboard and in the prompts CSV export. The [REST API](/api/overview)'s prompt analytics and the MCP `get_analytics` tool return the rates, not the standing. The MCP `query_evidence` tool can group and filter prompts by standing (`state`: `won`, `contested`, `lost`, `absent` or `unmeasured`) under the same rule, but it does not return the per-model cells.
### One model: the cell
Each model a prompt is read on gets one cell. The cell is the state of the **latest successful reading** on that model in the window you are looking at. A reading is the answers from one day's run; its n is the answers that came back successfully. Failed answers have no text, so they have no state and are left out of n.
| Cell | Name | Over the n answers in that reading |
| - | - | - |
| ● | Named and linked | You are named in at least half of them, and your domain is cited in at least half of them. |
| ◐ | Named | You are named in at least half of them. |
| ✕ | Lost | You are not, and one tracked competitor is named in at least half of them. |
| ○ | Absent | Neither you nor any tracked competitor clears half. |
| — | Not measured | There is no successful reading on this model in the window. |
"At least half" rather than "at least once", because a cell is a label on a small sample, and a label one answer in three can flip would move inside the sampling noise. Ties go to you: named in 1 of 2 answers is **Named**.
A competitor counts only when the answer names it in its own words. A competitor whose site is cited as a source but who is not named in the text does not make the cell **Lost**.
The cell shows the latest reading's standing, while the rate beside it covers the whole window. The two answer different questions: where you stand now, and how often you were named over the period. Hover a cell to see its evidence:
* **Named and linked:** "ChatGPT: named and linked to your site in the latest reading (3 answers, last read Sep 29)."
* **Lost:** the competitor it was lost to and when it was last read, for example "ChatGPT: lost to rival.com, named instead of you in the latest reading (3 answers, last read Sep 29)."
* **Not measured:** the reason, for example "ChatGPT: not measured, not read in this window." The other reasons are that every read in the window failed (with the commonest failure reason when there is one), that no answers came back, or that this prompt is not read on that model. [Reading the numbers](/measure/reading-the-numbers#how-does-the-dashboard-show-a-zero-and-a-dash) lists them.
**Absent is a measurement. Not measured is the lack of one.** ○ Absent means we read the answers and nobody you track was named. — Not measured means we have nothing to read. A prompt we never managed to read is never shown as absent.
### Across models: the prompt's standing
The **Standing** column folds the cells into one state for the prompt. Only measured cells count: a model we could not read neither helps nor hurts the prompt, because counting it would turn a gap in our coverage into a claim about your brand. Call the number of measured cells M.
| Standing | Rule, checked in this order | Hover text in the dashboard |
| - | - | - |
| **Won** | Named (● or ◐) on at least half of the M models, and ● Named and linked on at least one | Named on at least half of the engines read, and linked on at least one. |
| **Contested** | Named on at least one model | Named on some engines, but not enough of them to have won it. |
| **Lost** | Named on none, and ✕ Lost on at least one | A tracked rival is named and you are not, on at least one engine. |
| **Absent** | Every measured cell is ○ Absent | Nobody you track is named — not you, not a rival. |
| **Not measured** | M is 0 | No engine has a successful reading in this window. |
For example, a prompt read on four models with cells ●, ◐, ✕ and — has M = 3. It is named on two of the three, which is at least half, and linked on one, so it is **Won**, and the column adds "lost on 1 of 3".
A measured standing that is lost on any model shows "lost on X of M" under it. That count is what **Losses first** sorts by.
### The prompts grid
**Analytics → Prompts → By prompt** shows every tracked prompt against every model, one column of cells per model, beside the Standing column and the four core rates (Mentions, Share of voice, Avg position, Citations). The rows also show topic, intent, monthly search volume, a trend, the answers read (`n`) and a Last change column. A column with no value for a row shows a dash with its reason, for example "Intent is not classified yet." On this board the Last change column is a dash on every row, with the reason "No shipped change of ours is linked to this prompt on this board yet."
* **Losses first** is the default order: prompts lost on the most models first. Ties go to the prompt with more monthly searches, with unknown volume last, and then to the order the board was loaded in, so a reload never swaps two rows. Any column header sorts too. The first press on a model's column puts the worst first: ✕, ○, ◐, ●, with — last in either direction.
* **Group by** Topic, Intent, Standing or Market. Each group can collapse, and its header shows the number of prompts in it (`n = 12 prompts`), the count in each standing, worst first ("3 lost · 1 contested · 2 won"), and the group's pooled rates with the answers behind them. Grouped by standing, the groups run Lost, Absent, Contested, Won, then Not measured.
* **Filter** by Standing, Engine (the model), Topic, Intent, Asked from (the country) and Last read. Filters, the sort and the grouping are all in the page URL, so a link you share opens the same cut in the same order.
* **Pinning one model** recomputes every row from that model's cell alone, so the Standing column then answers for that model: ● is Won, ◐ is Contested, ✕ is Lost, ○ is Absent.
* **Wide grids** scroll sideways inside the grid, with the prompt column pinned. The page itself never scrolls sideways.
The prompts CSV export carries the cut it was taken under and the same standing. Each row has `state` and `lost_on`, and for each model `_state` (`named_linked`, `named`, `lost` or `absent`), `_state_n`, `_lost_to` and `_state_at`, beside that model's share of voice and its `_n`. An empty state means not measured, which is not absent.
## The prompt page
Opening a prompt leads with three sections, in this order:
1. **How to win this answer**: the [win plan](/measure/win-this-answer). The next step on the engine with the most answers read, the deciding sources for each engine with their citation share and whether you are on them, and when the answer will be read again ("after the first action lands" when nothing is scheduled). **Have Attensira draft it** starts a chat that drafts the work for your approval.
2. **Work on this prompt**: items in [Work](/agent/inbox) whose proposed change targets this prompt, marked when they need your OK.
3. **Before and after**: [re-read](/measure/before-and-after) results for this prompt only, each with its before and after samples, their date windows and an outcome (moved, no change, inside the noise, or not enough baseline).
Below them are two answers on the same engine, one from then and one from now. The older one is labelled **Before it shipped** only when it really predates the change; otherwise it is labelled **Then**. Each answer is labelled "read once on" and its date, and the page says so plainly: two single answers show changed wording, not movement.
The existing readings sit in tabs under those sections: **Answers and sources** (the default), **How it moved**, **Per engine** and **Competitors**.
With one model pinned, the prompts CSV export leaves `avg_position`, `failed_reads` and `last_read_at` empty, because they are not recorded per model, and the file says so in its header.
Next: group related prompts with [Topics](/tracking/topics), and choose which surfaces answer them in [Models](/tracking/models).
# Topics
Source: https://docs.attensira.com/tracking/topics
Group related prompts so reporting rolls up by theme instead of one question at a time.
## What is a topic?
A topic is a named group of prompts that belong to the same buying question. You assign a prompt to a topic, and Attensira rolls the per-prompt numbers up into a single set of rates for the group. Nothing about how a prompt runs changes — topics are a reporting structure, not a scheduling one.
The reason to use them is statistical as much as organizational. One prompt on Starter gives you one reading a day; ten prompts in a topic give you ten. A single prompt swinging from 0% to 100% is often noise. The same swing across a topic is a signal you can act on.
In the wire format a topic is `group_id`, but the `get_analytics` area enum value is `topic`, and `POST /v1/prompts` files a prompt under one with a `topic` field. The published vocabulary and the enum agree; only the identifier field kept its original name.
## What changes when I group prompts?
Reporting rolls up. Instead of reading forty prompt rows, you read six topic rows, each carrying a share of voice, a citation rate, and competitor rates computed across every successful run of every prompt in the group.
That rollup is a pooled rate, not an average of averages. A topic with two prompts — one that produced three successful runs and one that produced nine — weights the twelve runs together. This is what you want: the topic answers "across the questions in this theme, how often were we named?"
Individual prompt rows do not disappear. Grouping adds a level of reporting above them; you can still open any prompt and see its own rates, its citations, and the source domains the models drew on.
A prompt without a topic still runs and still reports. Topics are optional. Prompts left ungrouped appear only at the prompt level.
## The starter set
A new workspace does not open on an empty grid. At signup the agents choose its first prompts and file them into named topics, so by the time you reach the competitors step it already tracks about fifty prompts in four to eight topics. On the goals step they are shown collapsed: one line with the count, opening to the topics and then to the prompts.
The prompts come from three sources, and the order is the precedence:
1. **The free audit's prompts**, word for word, if you came from the [free audit](/start/free-audit). Rephrasing them would make the product's first number disagree with the one that brought you here.
2. **Prompts suggested by the analysis of your site.**
3. **New prompts the agents write** from your business profile. These are phrased the way a buyer asks an assistant, 4 to 14 words, and never name a brand, yours or anyone else's: an unbranded question is the honest test of whether a model reaches for you unprompted. Topic names never name a brand either.
Duplicates are removed, and the set is capped at the prompt slots you have left: your plan's, or Starter's 50 before you hold a plan, less the prompts your other workspaces already track. When the cap bites, the audit's prompts are kept first and the agents' own are trimmed evenly across topics. A chosen prompt that fits none of the proposed topics goes into a topic called **More questions**.
When the agents cannot tell what your business does from your site and profile, they do not guess. The starter set is then the audit's and the site analysis's prompts only, in one topic called **Starter questions**, and the goals step says why. Fill in your business profile and they can propose more.
The starter set is created once. After that it is an ordinary set of prompts and topics: add, remove, reword or regroup any of them, from the prompts screen, chat or MCP.
## How should I structure topics?
Group by the question a buyer is asking, not by the feature you want to sell. Buyers arrive at assistants with a job to do, and the topic that predicts your outcome is the job, not your roadmap.
Three structures that hold up:
* **By stage.** Discovery prompts ("what tools exist for X"), evaluation prompts ("X vs Y for a team of 20"), and objection prompts ("is X hard to migrate to"). These behave very differently — discovery answers name many brands, evaluation answers name few.
* **By segment.** The same question asked for a solo operator, a 20-person team, and an enterprise. Models often recommend entirely different products across these, and a blended number hides it.
* **By use case.** One topic per problem your product solves. This is the structure that maps most cleanly onto content work, because a weak topic points at a page you need to write.
Keep topics large enough to be stable — five to fifteen prompts is a workable band — and resist a topic that holds one prompt, which reports exactly what the prompt already reported.
## How do I create and change topics?
Use **New topic** on the Prompts or Topics screen, or let the agent propose topics when you [paste prompts](/tracking/prompts#how-do-i-add-prompts). Name it in the words a buyer would recognize. The name appears on every rollup row, so make it readable without your internal shorthand.
A prompt belongs to one topic at a time. Assigning it to a new topic moves it rather than copying it. File prompts as you add them, move one from its own page, move many with the [bulk bar](/tracking/prompts#moving-or-deleting-many-prompts-at-once), or drag them between groups (below).
Topic rates appear once the prompts in the group have successful runs. A brand-new topic reports `value: null` until then.
Regrouping is retroactive, and it takes effect at once. Nothing is stored per topic: every topic number is computed when you read it, from the stored runs and the topic each prompt is filed under **now**. Move a prompt and the next read of either topic counts that prompt's whole history in its new topic, with nothing to rebuild and no wait for the next reading. The same holds everywhere a topic is a cut: the boards, sentiment by topic, `query_evidence` grouped or filtered by topic, and `answer_slice` filtered by topic. Historical topic charts will therefore shift when you reorganize. Do a reorganization in one sitting rather than drifting prompts between topics week to week, or your trend lines describe your bookkeeping instead of your market.
Deleting a topic does not delete its prompts. They become ungrouped and keep reporting individually.
## Merging, splitting and dragging
The structure the agent proposes, or the one you drift into, rarely stays right. Three reorganisations fix it without re-adding anything:
* **Merge into…** is for two topics that turned out to be one question. Every prompt of this topic moves into the topic you choose, with every reading, this topic is deleted, and you land on the topic you merged into.
* **Split…** is for one topic that turned out to be two. Name the new topic and tick the prompts that go to it; the rest stay. The new topic takes the engines and the country of the one it came from. A split needs at least two prompts and must leave at least one behind; taking every prompt is a rename.
* **Dragging** works on the Prompts grid grouped by Topic: drag a row onto another group's header, or onto the no-topic group to take it out of every topic. A selected row carries the whole selection with it. Dragging is a shortcut for a pointer; on a keyboard or a touch screen, use the prompt's **Move** or the bulk bar's **Move…**, which make the same change.
Merge into… and Split… are in the menu on a topic's own page (**Analytics → Topics**, then the topic), offered when there is another topic to merge into and when the topic has at least two prompts.
Every regroup (moving one prompt, a bulk move, a drag, a merge or a split) says the same thing before it happens: "Grouping is retroactive: topic numbers for past days will recompute. Prompts keep the engines they’re read on." Both halves hold for all of them:
* **History recomputes.** Topic numbers are worked out from the stored runs and where each prompt is filed now, so the topic a prompt lands in reports its whole history and the topic it left stops reporting it. Nothing needs rebuilding, and nothing waits for the next reading.
* **The engines do not change.** A moved prompt keeps the engines it was read on, whichever topic it lands in, so a reorganisation never starts or stops reading a prompt on an engine. That is why a bulk move, a drag, a merge and a split still work when your credit balance is at zero; moving a single prompt with its own **Move** needs a balance above zero, so at zero, tick it and use the bulk bar's **Move…** instead. The market is the exception: a prompt without a market of its own is read in the markets of the topic it lands in. A split's new topic copies its source's country but not a list of markets set on the source topic (its **Markets** menu item); set that on the new topic if the split prompts should stay in those markets.
None of the three is all-or-nothing, and each says what happened:
* A merge that could not move every prompt keeps the source topic, so nothing you asked to move is left without a topic. It says how many moved and how many did not; merge again to finish.
* A prompt added to the source topic while a merge is running is never deleted with it: it ends up with no topic.
* A split that created its topic but could not move some prompts says so; those prompts stay in the original topic, and you move them from the prompt list.
## Reading a topic rollup
Topic rates use the same shapes as everything else. Rates come back as `{value, n}` where `n` is the count of successful runs behind the number, and `null` means not measured rather than zero. Deltas come back as `{value, real}` and are suppressed to `{real: false, value: null}` when a two-proportion z-test at 95% cannot separate the change from noise.
A topic's `n` is the sum of its prompts' successful runs, so topics cross the noise floor sooner than the prompts inside them. That is the practical payoff: a topic can prove a change in a week that a single prompt would take a month to demonstrate.
Competitor rates roll up per topic too — and, as everywhere in Attensira, they share the same denominator as your own share of voice and do not sum to 100%. See [Competitors](/tracking/competitors) for why.
# AI traffic
Source: https://docs.attensira.com/traffic/ai-traffic
AI crawlers fetch your pages; models answer prompts. Two systems, two numbers, and why measurement lives in edge middleware.
AI traffic is available on Growth, Business and Agency.
## Crawlers and models are not the same thing
A **crawler** is a bot that fetches your pages over HTTP. A **model** is the system that answers a prompt for a person. They belong to different pipelines, run on different schedules, and produce different numbers. A page can be fetched constantly and never cited, or cited from something a model learned months ago and never re-fetched.
So do not reconcile the two. Crawler counts in AI traffic and citation counts in [Pages](/traffic/pages) are not two views of one quantity, and a rise in one does not predict the other.
## Why measurement has to happen in edge middleware
Crawler visits are only visible on the server, before any page renders. That is a hard technical constraint, not a design preference:
> AI crawlers do not execute JavaScript. A client-side beacon, no matter how it is written, has never seen one and never will: by the time the script runs, the only visitors left are humans with browsers.
Edge middleware runs on every request, including the ones that never become a rendered page. That is the only layer where a crawler fetch exists to be counted.
Any measurement approach that depends on a script in the browser is measuring humans. If your existing analytics shows zero AI crawler traffic, that is what it is telling you about itself, not about the crawlers.
## How do you install it?
Attensira generates the install instructions for your workspace in **Settings**. They include the ingest endpoint and the credentials tied to your workspace, so use the generated copy rather than an example you found elsewhere — a snippet without your workspace's values sends data nowhere.
Find the AI traffic section for your workspace.
They are written for the edge layer of your stack and already carry your workspace's values.
Nothing appears until a crawler actually fetches a page. On a low-traffic site that can take days.
## What this data does and does not tell you
It tells you that requests arrived, when, and against which paths. Read it as a coverage signal: pages that are never fetched cannot become fresh citations, and a section that crawlers never reach is worth checking for robots rules, auth walls, or redirects.
What it does not give you is verified attribution. Attensira does not claim to prove that a given request came from a specific named crawler. User agents are self-reported and trivially spoofed, and Attensira does not publish a verified-bot claim it cannot stand behind. Treat the data as directional evidence about coverage, not as an audited log of who visited.
Crawler activity is not evidence of visibility in answers. For whether models actually name or cite you, read [Pages](/traffic/pages) and your Mention and Citation rates.
## How long is it kept?
Two layers, kept for different lengths:
| Layer | What it holds | Kept for |
| - | - | - |
| Raw requests | Each request as it arrived | **90 days** |
| Daily rollup | Per day, per page and per agent: how many requests, why the agent came, and whether the visit was referred by an AI assistant | **Indefinitely** |
The rollup is what lets a before-and-after reach back further than 90 days: whether crawlers read a page more after a change you shipped in March needs March's numbers in June. It holds no field that identifies a person: no user agent string, no referrer and no IP-derived value, only the counts and the assistant a referral resolved to.
How a day gets into the rollup:
* Every night, the last **7** completed days are folded again, so the days a dashboard reads first are never more than a night behind.
* A day gets its **final** fold when it is 88 days old, two days before its raw requests expire. A request stamped with a date older than that is refused when it arrives, because it could never reach the rollup.
* The first time your workspace is folded, every day still in raw retention is folded, so your history starts with the full 90 days you already had.
A window up to 365 days long can be read. Days inside the last 90 come from the raw requests and older days from the rollup, with no day counted twice. A window reaches back only as far as your history is complete: asking for more returns the longest window there is, and the response says how many days of history you have. A change is compared against the previous window of the same length only when that whole window exists; otherwise no change is shown, rather than a drop that is really the start of your history.
### Days are UTC days
Traffic days run from UTC midnight to UTC midnight, and every response says so (`day_basis: "utc"`). Answer readings use your workspace's own calendar day instead. Near midnight, a crawler fetch and an answer from the same evening can land on different days. A window that reaches into the rollup starts at a UTC midnight, so every day in it is a whole day.
## Privacy
Requests are measured at your own edge, on your own traffic. The rollup kept beyond 90 days holds counts only, with nothing that identifies a person. See the [privacy policy](https://attensira.com/privacy) and [terms of service](https://attensira.com/tos).
# Pages
Source: https://docs.attensira.com/traffic/pages
See which of your own URLs models attach as citations, how often, and where they land in the citation list.
## What does the Pages table show?
Pages lists the URLs on your own domain that models attached as citations when they answered your tracked Prompts. One row is one URL. A URL appears only after a model actually cited it in a successful run inside the window you are looking at — this is not a crawl of your sitemap.
A **Citation** is a single URL a model attached to an Answer. A **Mention** is your brand named in the answer text with no link. Pages counts citations only, so a page can be doing useful work in a market where your brand is named constantly and still show few rows here.
## How do you read each column?
Each row carries the URL, how many citations it collected in the window, its share of your domain's citations, and its average position in the citation list. Read them together: a page with few citations but a strong average position behaves differently from one cited often and always last.
| Column | What it counts | What it does not mean |
| - | - | - |
| Citations | Times this exact URL was attached to an answer | Not visits, not clicks, not crawler fetches |
| Share | This URL's citations divided by all citations to your domain in the window | Not a share of the whole market |
| Avg position | Mean index of the URL inside the model's citation list, computed per source domain | Not a rank of your brand against competitors |
Average position is a per-source-domain measure. It tells you where your URLs tend to sit in a list of links, not how you place against any competitor. There is no brand rank or position metric in Attensira.
## Why is a number blank instead of zero?
Every rate in Attensira is a `{value, n}` pair, and every delta is a `{value, real}` pair. A blank `value` means **not measured** — no run produced data for that cell. A `0` with `n` above zero is a real, measured zero: runs happened and the URL was never cited.
Deltas are tested for significance with a two-proportion z-test at 95%. When a change falls below that noise floor you get `{real: false, value: null}`, which reads in the table as no change we can prove. It is not a claim that nothing moved, and it is never a zero.
Treat a blank and a zero as different facts when you report internally. Rewriting "not measured" as 0% invents a failure that the data does not support.
## Why does a page you care about not appear?
The table is bounded by what was queried. If a Prompt that would surface the page is not in your workspace, no run touched it. If the Model that cites it is not tracked for your workspace, it was never asked. A new workspace tracks four model channels by default — ChatGPT, Perplexity, Claude and Google AI.
Sampling depth also bounds what you see. Each Prompt × Model × country runs at most once per workspace-local calendar day, with n=1 on Starter and n=3 on Growth, Business and Agency. Rare citations need more samples and more days before they show up at all. Failed runs are stored but excluded from `n`, so a bad day narrows the window rather than poisoning it.
Retention limits how far back you can look: 3 months on Starter, 12 on Growth, unlimited on Business.
## What to do with the table
These are the pages models currently trust to answer your Prompts. Keep them accurate and current before writing anything new.
If models cite a support article where you expect a product page, the product page is not answering the question the Prompt asks.
A URL consistently late in the citation list is being used as corroboration rather than as the answer.
At n=1 per day, single-day movement is mostly sampling noise. Read weeks, not days.
## How does Attensira know which pages you have?
The Pages table above is citation-driven, but Attensira also keeps a separate inventory of every URL your site publishes, so the agent can reason about pages models have *not* cited yet.
That inventory is a map of your domain made by our web reader, and your home page is always included so a small site still has one page on file. When the web reader is unavailable, the map falls back to what your site publishes: `/sitemap.xml`, or the sitemaps your `robots.txt` declares in `Sitemap:` lines. In that fallback, a page that is not in a sitemap is not discovered.
Page reads fall back the same way, to a plain HTTP fetch with no JavaScript rendering. A page built entirely in JavaScript may then come back empty, and the agent says so rather than guessing what it says.
Discovery re-runs **once a day**, so pages you publish after signing up are picked up on their own. It is unmetered: finding out that a URL exists costs you nothing.
Reading each page is the separate, metered step. Attensira fetches a page and keeps a version of it: its title, meta description, headings, structured data, structure and visible text. It re-reads a page after **7 days**, or about daily while one of your open suggestions targets it, a capped batch at a time so a large site is worked through steadily rather than all at once. A read that finds the page different records a change; see [Page changes](/measure/page-changes). Full-text analysis of a page happens only when you or the agent explicitly asks for it.
Two different things are called "pages" in this product, and they are worth keeping apart: the table on this screen counts pages **models cited**, while the inventory described here is every page **your site publishes**. A URL can sit in the inventory for months without ever appearing in the table.
## Related
Crawler fetches of your pages, which are a different system from model answers.
Ask the agent to explain a page's citation pattern in context.