Permission scopes
Choose the least API-key permissions required for ingestion, investigation, response, and administration.
On this page
API-key scopes use resource:action. A key can receive only known scopes held by the member creating
it. Project scope and permission scope are both enforced: neither can widen the other.
The authenticated scope catalog at GET /api/v1/account/api-keys/scopes and the key form under
Agents and keys in the Console is authoritative. The groups below describe the current selectable catalog.
Key bundles for agents
Use anectico apikey create --scope-profile <bundle> to create a management key from a bundle.
The key stores explicit scopes. Changing a bundle later does not change an existing key.
| Bundle | Scopes | What it is for | Who can create it |
|---|---|---|---|
investigate |
34 | The default for investigation. Reads evidence, model costs (llm:read) and stored evaluations (evals:read). |
Any member |
incident |
57 | All investigation reads, plus alerts, incident response, releases, silences and tickets. | Owner or admin |
agent-observability |
13 | Agent operations, governance and evaluation writes. It does not include the full investigation bundle. | Owner or admin |
admin |
2 | MCP read and write umbrellas only. Add the exact scopes with --scope; the bundle cannot stand alone. |
Owner or admin |
The default bundle changes no settings and runs no evaluation or model. Reading a stored score
does not create a score. A frozen-case replay can return recorded prompts and answers with
agents:content:read, which the default bundle already includes. It invokes no model or tool.
No bundle includes deletion scopes. A viewer cannot create a key.
See Scope recipes for narrower keys and extra permissions.
Observed customer reliability
Person latency rank, exact reliability detail and metadata preview require persons:read and
traces:read. Optional profile display separately needs persons:profile:read and current profile
policy. A withheld profile does not become a measurement refusal; exact detail currently has no
profile output field and declares that redaction.
Account latency rank, exact detail and preview require groups:read, traces:read and
agents:content:read, plus current project policy permitting both request-stamped account
attributes. A denied branch names the missing authority and refuses; account membership cannot
replace it. Current account owner state also gates disclosure. Unavailable owner or source checks
do not establish a healthy or empty account. Account reads expose no profile flag.
MCP adds mcp:read for all six gateway actions. The same-definition project comparison is part
of account detail and needs no additional services:read. There is no separate reliability scope.
These are optional branches in customer investigation and weekly review, so losing account
grants does not prevent their main jobs. See the read guide,
redaction and evidence boundaries.
Account financial authority
The account business contract reserves groups:business:read and groups:business:write for
owners and admins. Declaration management and value reads are available through the
account-value workflow. No default agent bundle includes either.
Ordinary groups:read and groups:write never grant access to these declarations.
| Job contract | Required domain scopes |
|---|---|
| Read, list, export or recover a declaration write | groups:read, groups:business:read |
| Set, clear or batch replace a declaration | Both read scopes above and groups:business:write |
| Explicitly create a missing account during a declaration | All declaration-write scopes above and groups:write |
| Declared value affected or value triage | errors:read, persons:read, groups:read, groups:business:read |
| Incident renewal exposure | All value-triage scopes above and incidents:read |
| Account model spend | llm:read, persons:read, groups:read, groups:business:read |
| Explicit business enrichment of existing results | That result's existing scopes and both account read scopes |
MCP additionally needs mcp:read for every action and mcp:write for mutations. Export separately
requires the project's current account-property policy to allow both reading and exporting on
every page; export:read alone grants neither. Export allowance is denied by default.
An owner or admin can explicitly approve the financial read scope for an OAuth agent, within that
connection's existing read-only ceiling. Host OAuth cannot carry groups:business:write,
groups:write or mcp:write. Financial writes need an owner-created management key with the exact
scopes above. Use one project-bound management key for automation; its key purpose and intake
purpose are both unspecified. There is no separate management-purpose value. Capture, native
intake, application and client delivery keys cannot carry either financial scope. A missing
financial permission refuses an explicit financial job; an unavailable value is never zero.
See Declared account value for the canonical financial
interpretation, export policy and exact credential workflow.
Telemetry and investigation
Problem reports from a browser reuse its project-scoped browser_capture key with only
analytics:write (mobile_capture is also accepted); they gain submission authority only.
These public keys cannot list, read or delete reports. Agent submissions still need ingest:write,
reads need errors:read plus agents:content:read, and deletion needs errors:delete.
native_ingest is for telemetry and is refused on the problem-report route with a clear 403.
| Resource | Scopes |
|---|---|
| Ingestion | ingest:write, ingestion:pipelines:read, ingestion:pipelines:write (write is owner or admin only) |
| Traces | traces:read, traces:delete |
| Logs | logs:read, logs:delete |
| Metrics | metrics:read, metrics:delete |
| Service health | services:read |
| Errors and Issues | errors:read, errors:write, errors:delete |
| Query and search | query:read, query:write, search:read |
| Customers and accounts | persons:read, persons:profile:read, persons:correct, persons:erase (owner or admin only), persons:export (owner or admin only), groups:read, groups:write, groups:delete, groups:erase (owner or admin only) |
| Account business declarations | groups:business:read, groups:business:write (both owner or admin only; separate from ordinary account reads) |
| AI agents and LLM cost | agents:read, agents:content:read, agents:write, llm:read |
| Events, cohorts, and flags | analytics:read, analytics:query, analytics:sql, analytics:write, cohorts:write, cohorts:delete, flags:read, flags:evaluate, flags:write, flags:delete |
| Tracking-plan configuration | tracking_plans:read, tracking_plans:write, tracking_plans:delete |
| Saved-insight definitions | insights:read, insights:write, insights:delete |
| Scheduled reports | reports:read, reports:write |
| Warehouse export | warehouse:read, warehouse:write |
| In-product surveys | surveys:read, surveys:write, surveys:respond |
| Anomalies | anomalies:read, anomalies:write, anomalies:delete |
| Replay | replay:read, replay:content:read, replay:write, replay:delete |
| Releases | releases:read, releases:write |
| Export | export:read, export:write, export:download (owner or admin only) |
errors:write allows Issue lifecycle actions and symbol uploads; deleting error data requires
errors:delete, which covers both units it has — deleting one Issue with its occurrences, and
erasing one customer's error occurrences across every identity they have used. Both are project-exact
and permanent, including for occurrences that arrive afterwards. errors:delete is not granted by
any of the narrower API-key presets (read-only, read-ingest, ingest-only) and must be named
explicitly — only the Full access preset, which grants the complete scope catalog, includes it.
Native OTLP keys require purpose: native_ingest and only ingest:write. Product capture,
identity and group controls require a separate browser_capture, mobile_capture or server_capture key with
only analytics:write. A management key holding these scopes still cannot enter public intake.
Session replay uses a separate management credential with replay:write. See
native intake keys for CLI and API setup and current SDK limits.
ingestion:pipelines:read lists sources, pipeline revisions and activation operations, runs
dry-run validation and preview (against a stored revision or an unsaved program; samples and
outputs are never stored or logged), and reads intake diagnostics (accepted, transformed, dropped, and refused
counts with reason codes) and collection health (whether a
source's collector is delivering); it is viewer-granted and returns no event content.
ingestion:pipelines:write creates, renames and revokes sources and stores and activates pipeline
revisions; it is owner or admin only and also requires the read scope. Neither scope sends telemetry or
creates keys. Collection health's credential facts (how many bound keys can still authenticate)
additionally need api_key:read; without it they read WITHHELD, never a guessed state.
See Ingestion sources and pipelines.
The same two scopes cover a project's
browser capture settings:
ingestion:pipelines:read reads them, and changing them, including turning autocapture off for the
project, also requires ingestion:pipelines:write.
Log-derived metric rules reuse metrics:read for every read (list, get, revisions, validate and
querying the published series) and additionally require ingestion:pipelines:write for every
mutation (create, revise, delete) — a rule changes what is derived from every log in the project, the
same trust boundary as a transform pipeline. See
Derive a metric from your logs.
Tracking-plan list or get operations are available through REST, MCP and CLI; create, update or delete
use confirmed MCP actions and CLI, with session-only REST for human clients. See Manage tracking plans. tracking_plans:read is viewer-granted; tracking_plans:write is member-granted;
tracking_plans:delete is owner or admin only. Writes and deletion also require the read scope for
configuration receipts. Capture and settings permissions do not grant plan management.
OAuth-delegated agents receive the read scope only; writes require a deliberately scoped API key.
Tracking permissions do not grant access to captured event values or change content permissions.
Instrumentation recipes list or get require tracking_plans:read. Applying a recipe selection
additionally requires tracking_plans:write, insights:read and insights:write — it composes a
tracking-plan merge and one saved insight per starter insight through those same owner permissions,
never a separate scope. As with tracking-plan and saved-insight writes, the REST apply route admits
only a current user session; API keys, including the CLI, use the confirmed MCP action, which also
requires mcp:read and mcp:write. See
Start from a recipe.
analytics:query authorizes typed analytics execution separately from reading events or results.
It also authorizes a preview of a measurement,
which needs the same permissions as the run and adds none. Both require analytics:read and every source permission used by the definition. It is granted
to organization roles including viewers and is eligible for delegated OAuth; it does not grant
cohort mutation, export, or access to otherwise restricted source content.
analytics:sql runs one read-only SQL SELECT over a project's own events, inside a required window.
It also requires analytics:read and persons:read (and mcp:read over MCP); reading the table
description needs only analytics:read. It is granted to members, developers, administrators and
owners, never to viewers, to agents connected over OAuth, or to Live screen publishers, and a key can
carry it only if the member creating the key holds it. It grants no data that analytics:read does
not already disclose: it grants your own computation over it, bounded in rows, time, memory and rate.
The events table's properties column is event content, so it additionally needs
agents:content:read; without it the column is absent and every other column still works. Every run
is recorded in the audit log. See Run read-only SQL over your events.
Saved-insight get, list, and search require insights:read. MCP and CLI create or update also require
insights:write; retirement requires admin-granted insights:delete. All mutation operations
require both mcp:read and mcp:write, preview and exact-argument confirmation. API keys can retire
only their own definitions; current admin user sessions can retire project definitions. These
permissions do not grant telemetry access. See Manage saved insights.
Scheduled reports use two scopes. reports:read (granted to viewers and above) lists and reads
reports, their runs and the report limits, and lets a signed-in member remove themselves from a
report's recipients. reports:write (granted to members and above) creates, replaces, pauses,
resumes, deletes and sends reports. The MCP actions that change or send an existing report also
need reports:read. Creating, replacing or resuming
a report makes you its owner, so it additionally requires insights:read, analytics:read,
analytics:query and persons:read (and dashboard:read for a dashboard); every run checks these
again for the owner. Neither reports scope grants access to the reported data. Pausing and deleting
need only reports:write. A preview to yourself needs a signed-in member, not an API key. MCP
actions also require mcp:read (reads) and mcp:write (writes). See
Send a scheduled report.
Warehouse export uses two scopes. warehouse:read (granted to members and above) lists and reads
destinations, exports and their runs, and the export limits. warehouse:write (granted to
administrators and above, because it sends data outside Anectico) adds, changes, tests and deletes
destinations, and creates, replaces, pauses, resumes, runs and deletes exports. The MCP actions that
change or run an existing destination or export also need warehouse:read. Creating, replacing or resuming an export makes you its owner,
so it additionally requires analytics:read and persons:read, agents:content:read when event
properties are exported, and for an audience the permissions the cohort's definition needs; every
run checks these again for the owner. An owner who loses agents:content:read keeps exporting,
without the properties column. Neither warehouse scope grants access to the exported data, and
neither can delete a file in your bucket. MCP actions also require mcp:read (reads) and
mcp:write (writes). See
Export events and audiences to your warehouse.
In-product surveys use three scopes. surveys:read (granted to viewers and above) lists and reads
surveys, their definitions, targeting and accepted-response counts, and the survey limits.
surveys:write (granted to members and above) creates, edits a draft, launches, stops, duplicates and
deletes surveys, and always requires surveys:read as well; launching puts a survey in front of
real visitors, so CLI and MCP both require a server preview followed by exact-argument confirmation.
surveys:respond (granted to members and above, not to viewers, because it records) is the SDK
half only: it lets a key ask which surveys one visitor may see and record a display, a response or
a dismissal. It implies neither of the others, and neither of them implies it, so the key embedded
in your page or app must be a client delivery key holding only surveys:respond
(apikey create --purpose client_delivery --scope surveys:respond): a key in a page is visible to
every visitor and must not be able to read a survey's targeting or change a survey. A client
delivery key may also explicitly carry flags:evaluate, but separate keys are the default. It
can carry no management, MCP, definition or content read scope. Never embed a management key.
None of the three grants access to results: every survey result also needs the event-content permission agents:content:read beside analytics:read, analytics:query and
persons:read, because what a person answered is customer content, and the same permission is
checked again on every read of the result, of the people behind a count and of the open-text answers.
See Run in-product surveys. The server enforces client-delivery purpose, complete identity
coverage and source-session propagation. Client recognition alone does not prove stored session
correlation on a deployment. Earlier browser or REST runs do not qualify the changed authority.
Use the prescribed purpose; a management or intake key cannot substitute for it. Mobile runtime qualification remains separate.
Derived audience creation and replacement additionally requires analytics:read, persons:read and
all inherited source permissions alongside cohorts:write. MCP and CLI mutations use both
mcp:read and mcp:write, preview and confirmation and a stable UUID key. Exact audience reads need
analytics:read, persons:read, inherited source permissions and mcp:read, but no write or query
scope. See derived audiences.
Creating, updating, refreshing, and replacing ordinary cohort membership requires cohorts:write.
This member-granted scope is separate from SDK capture (analytics:write) and is excluded from
external OAuth delegation. Application and browser SDK key presets do not grant cohort management. Deleting one is deliberately
separate under cohorts:delete, an owner or admin only scope, because deletion is destructive.
services:read names per-service request rate, error rate, and duration, plus the per-operation
breakdown. agents:read names AI agent-run summaries: which agent ran, its model, step and
tool-call counts, duration, token and cost totals, and outcome. It also names the server-owned
24-hour fleet summary and the agent inventory and evidence trail around it — declared and
telemetry-discovered agents, the review queue over
those discoveries, which telemetry belongs to which agent, step-level agent events, action receipts,
chain verification, and signed bills of materials. None of that admits prompt, completion or tool
content: agent events expose bounded identity and provenance metadata, never content or a content
digest. It also reads exact outbound-drive audit records and recorded-session replay results. Replay
counts, stop reasons, comparison kinds, sizes, and digests remain visible; the real user turns and
agent responses still require agents:content:read. llm:read names LLM spend — the
per-model summary, the cost series, the per-customer spend leaderboard, and the spend breakdown that
says who spends and why. The breakdown names the people only with persons:read, names a person’s
costliest run only with agents:read and persons:read together, and groups by span name (entrypoint) only with
traces:read.
agents:write is the one write in that family, and it is separate from every read in it. It names
declaring what you have deployed, the configuration that deployment resolved, the bill of materials
that configuration is made of — the three facts that let Anectico tell you when a running agent has
drifted away from the composition it was approved with — and what your agent actually did: one
lifecycle step of an action it took in the world, appended to your tamper-evident action log.
It also authorizes the explicit outbound control actions: opting one registered agent into driving,
sending one caller-authored message, and explicitly replaying one real recorded session. Starting a
session replay additionally requires agents:content:read because the action exports the person's
recorded words. Because these calls can trigger another system and spend its money, agents:write
is checked even when a trusted internal caller performs the transport work.
Nothing here can be inferred from telemetry. Your spans carry a configuration hash, and only the
system that resolved the configuration can say what that hash stands for; and only your agent knows
that it intended an action, was refused one, or could not confirm that an effect landed. So a release
pipeline declares its inventory and your agent records its own conduct, both with a key holding
agents:write and no dashboard involved. Reading any of it back — a configuration, a manifest, a
receipt — is agents:read, not this scope, and so is asking for a signed checkpoint of your action
log. It is owner or admin only to mint, because a declaration changes what drift detection watches.
The scope labels what you declare; it never lets you declare it as ours. Every artifact it produces records whose claim it is, inside the bytes the artifact's own digest covers:
| You declare | Recorded as | Anectico's own is |
|---|---|---|
| A bill of materials | authority: "declared" |
authority: "mediated" |
| An action-receipt step | receipt_authority: "reported" |
receipt_authority: "mediated" |
declared means Anectico attests to what you declared and when, not that the components are accurate.
reported means Anectico attests that it received this statement and when, not that the action
happened. mediated is reserved for what the platform itself composed or performed. You cannot choose
the value — it follows from the credential that made the call, and a request that sets one is
rejected. Because it is inside the signed or chained bytes, anyone you hand an exported record to can
tell the two apart with the published key and no call to us. (The REST authority and
receipt_authority response fields carry the same fact as the prefixed enum name —
ABOM_AUTHORITY_DECLARED/ABOM_AUTHORITY_MEDIATED and
RECEIPT_AUTHORITY_MEDIATED/RECEIPT_AUTHORITY_REPORTED — outside the signed or chained bytes.)
One action has one authority for its whole life. A step you report under an action Anectico performed is rejected rather than merged, so a receipt can never show your statement under our name.
Each of those three reads stands on its own scope. A key carrying only services:read,
agents:read, or llm:read reads that summary without also reading the spans underneath — that
separation is the point of the split. The same endpoints still accept traces:read, so a key that
already holds it keeps working unchanged; scope new keys with the specific read instead of
traces:read.
Three further scopes name recorded content rather than a summary of it, each separate from the scope that lists the same records:
| Scope | Names | Its metadata-only counterpart |
|---|---|---|
persons:profile:read |
A customer's profile properties — the traits your identify() calls attach, such as email, name, and plan |
persons:read (identity, aliases, activity timeline) |
replay:content:read |
Recorded session content: the replay snapshots and their console and network payloads | replay:read (recording list and metadata) |
agents:content:read |
Agent-run content: prompts, completions, tool arguments and results, the reasoning transcript, and exact recorded-session replay turns and responses. It also covers ordinary request detail: every span, log and metric attribute that is not on the list of operational keys, which includes a request's path (url.path), the session ID on a span (session.id) and your own custom attributes, and the property values of product events |
agents:read (run and replay metadata, comparison, and cost) |
Treat all three as personal-data scopes. Replay masking is configured in the SDK and is best-effort, and an agent transcript contains whatever your end user typed plus whatever the agent retrieved for them, so a key that reaches this content reads it in its raw form.
Each content scope is required for the content it names, everywhere. The metadata-only counterpart no longer reaches it, on any surface — not the API, not a proof page, not the MCP endpoint. Concretely:
replay:readlists recordings and reads their start, end and duration. It does not open one: playback and the panel of errors and events captured during a session needreplay:content:read. It does read the kind and the position of each step of a recording (get_replay_steps); the target, the page address and the text of a step needreplay:content:readtoo. See Read the session as steps.- The masked page copies a heatmap is drawn
over follow the same split:
replay:readlists which pages have one,replay:content:readreads the copy itself (without it the heatmap shows its abstract page),replay:deletedeletes one, and the browser SDK uploads them with thereplay:writekey it uses for recordings. agents:readgives a run's summary — agent, model, steps, tools, duration, tokens, cost, outcome. The run's transcript needsagents:content:read.persons:readgives the identity spine — who exists, their aliases, their activity counts and timeline. It also admitsfind_struggling_people, which then measures each signal only with the read scope of that signal's data and reports the others as withheld; see Find who is having a bad time.find_silent_peopleworks the same way:persons:readadmits it, each failure signal and each report source needs the read scope of its own data, and a report source it may not read is withheld, never counted as "no report". Recorded support complaint metadata needserrors:readandpersons:read; it does not need content permission and never implies complete external support coverage. Their profile properties needpersons:profile:read, and so does anywhere those properties are used or matched: without it, an issue's affected customers and the LLM spend leaderboard identify people by their raw identifier instead of their name or email, and the people search matches identifiers only, so you cannot search by name. (An email address in a profile is stored redacted, so no permission makes it searchable; see What Anectico redacts and withholds.) (Whether a person is identified is part of the spine and is always shown — hiding it would misreport identified customers as anonymous.)
Some requests refuse outright with 403; others return the record with the restricted field
omitted, so the surfaces that carry both — the people list, an issue's affected customers, an agent
run's scores — keep working for every role.
On a proof page, a refusal costs you that panel and nothing else. The three proof pages that carry restricted content are assembled from several reads, and the ones you are allowed keep rendering:
| Proof page | Still shown | Replaced by a short note |
|---|---|---|
| A customer | Their identifiers, first and last seen, the counts for events, errors, traces, logs, replay sessions and LLM spend, and the full activity timeline | The profile properties panel, and the accounts they belong to |
| An agent run | The run's shape — agent, model, status, steps, tool calls, errors, duration, cost — plus its quality scores and related errors | The span timeline, which carries the transcript |
| A session recording | Who the session belongs to, when it started, how long it ran, how many segments it has | Playback, and the product events captured during the session |
The note names what is hidden and says to ask an organization owner or admin. It never offers a retry: nothing is broken, and the same request would be refused again. An outage looks different — it says so and gives you a Retry.
The same rule follows the content wherever it is retold. An automated quality score's written
explanation, where a result has one, was written by a model that read the run's transcript, so it
needs agents:content:read; the numeric score and its label do not.
Alert labels follow it as well, and this one bites in a place people do not expect. An alert rule
grouped or filtered on a customer field labels its alerts with that field's value — an alert grouped
by user_email carries an email address. alerts:read admits the alert, not the address: the
customer-identifying labels (user_email, user_id, user_ip_address, and the customer
identifier) are returned only to a key that also holds the read scope of the signal the rule
queries, such as errors:read. Two things follow from that:
- Alerts whose labels are withheld also come back with an empty
fingerprintandgroup_key. Those are digests computed from the full label set, so returning them would let a caller confirm a guessed email address. Alerts that carry no customer-identifying label are unaffected, and grouping and de-duplication behave exactly as before either way. - Notifications never carry them. A Slack message, an email or a webhook goes to a destination
with no permissions of its own and cannot be recalled, so those labels are left out of the
delivered message and out of the stored delivery record — for every recipient, regardless of who
configured the channel. Alerts still fire, still de-duplicate and still deep-link; open the alert
in Anectico to see which customer it is about. Silences are unaffected: a silence matching on
user_emailstill matches.
Browser measurements
Traffic reads require analytics:read and persons:read; Web Vitals reads require
metrics:read. MCP also requires mcp:read. Both use one explicit project and a
bounded retained window. Route and service labels are the route templates and service
names your application declares to the SDK; they are readable with these same scopes
and need no content permission. Read more in
browser experience.
Browser session evidence requires query:read, then checks traces:read,
errors:read and replay:read independently. Missing source permission produces
WITHHELD, not a claim that the session has no evidence. Only native identifiers
and matching timestamps are returned. Replay presence is resolved using the
customer's credential and does not grant replay content access.
Computed answers follow the data they were computed from
Anectico computes things for you — a cohort's membership, the accounts a segment matches, the customer an alert fired on, a feature-flag decision. Each of those is an answer to a question asked of your customer data, so each one needs the scope that admits the data behind it, not just the scope that admits the page it appears on.
| A key reading… | also needs |
|---|---|
| Cohort members, member counts or a cohort preview built on a profile property | persons:profile:read |
| Cohort members, member counts or a cohort preview built on events | analytics:read; add agents:content:read for event-property filters outside the exact operational-metadata allowlist |
| An account segment's matched accounts, or flag targeting on account properties | groups:read |
A trend, funnel or retention result measured with the account unit (unit.kind: "account") — the result itself, its participants, its contribution drill-down, and any export |
groups:read |
A trend, funnel, and retention result broken down by a person property (breakdown.source: "person") |
persons:profile:read |
A trend, funnel, and retention result broken down by an account property (breakdown.source: "account") |
groups:read |
What you will notice:
- A flag decision never varies with something the key cannot read. Without the scope behind a cohort or an account property, that targeting condition simply does not match — the same answer an unmatched condition has always given. Give SDK keys that target on cohorts or account properties the matching scope; leave it off keys that do not.
- A member count reads as
0when the key lacks the scope, and a member list is refused. A cohort of "everyone whose email is X" has one member or none, so showing the count would be showing the answer. Zero alone therefore does not prove an empty cohort. MCP refresh receipts mark this ambiguous value as unknown and omit the count change; an authorized member-page response can establish the exact count for the returned generation. - Per-entity alerts hide which customer they are about from a key without the scope — the id,
the label, the deep link and the customer's name in the alert text all go together. The alert
itself, its severity and its value are unchanged. Cohort-derived alert identities require
analytics:read,agents:content:readandpersons:profile:read, because the alert mirror does not carry the precise cohort predicate. - A person or account breakdown's labels are re-checked on every read, not just when the
measurement first ran: revoke
persons:profile:readorgroups:readand a key that already holds an already-published result's link stops seeing the breakdown labels on its very next read. An event-property breakdown needs no such scope and is unaffected.
One limit worth knowing: changing a key's scopes changes what it can read, not what has already been sent. The notification you configured still names the customer — you chose that destination deliberately, and a Slack message or email cannot be recalled. If a destination should stop receiving customer identities, change or remove the alert rule that feeds it; narrowing a key's scopes will not do it.
- What you wrote is still yours to read. Cohort definitions, segment predicates and cohorts you uploaded by hand are not affected — only the computed answer is.
Every role from member upward already holds all three scopes, so nothing changes for people signing in. This binds narrowly scoped API keys, and one role: a viewer can no longer list the members of a cohort built on a profile property.
A signal read scope no longer reaches customer content. It used to. Prompts, completions, tool
arguments and retrieved documents recorded as span attributes by third-party AI instrumentation
came back verbatim from the raw trace endpoints to any key holding traces:read. They do not any
more: those attributes are withheld unless the credential also holds agents:content:read, and the
response says so — the span carries an anectico.content.withheld attribute naming exactly which
keys were removed, so a missing body is never confused with a call that had none. Over MCP the same
names are in the result's redactions list, each with the scope that reveals it.
The name of the scope is narrower than what it covers. Below agents:content:read only
attributes on a fixed list of operational keys are returned: token counts, model names, latency,
status, route, service and similar. Every other attribute is withheld. That includes a request's
path (url.path), the session ID on a span (session.id), your own custom attributes, and the
property values of product events. A key that holds traces:read and analytics:read therefore
reads requests and events without that detail, and the result says which fields it left out.
The same is now true of logs and of metric exemplars, and this page previously said the opposite.
A log record's attributes and resource maps, and the filtered_attributes on a metric exemplar,
carry the identical convention keys and are withheld from a credential without
agents:content:read — on log search, on a single log entry, on the surrounding-context window, on
correlation, and on the live tail. The same anectico.content.withheld marker names what was
removed. So a prompt recorded as a log attribute is now governed by the content tier wherever you
put it, and you no longer have to record model content on spans for that to be true.
Discovery and grouping follow the same rule as record reads. Log-field discovery omits a
content-classified field together with all of its sampled values. Metric-label discovery omits the
classified label; a metric query that filters or groups by that label is refused before reading its
values unless the credential holds agents:content:read and the project's current read policy
permits the class. Ordinary labels and fields are unchanged.
Two things this deliberately does not withhold. The log message itself stays readable on
logs:read: a log line is not automatically a model transcript, and treating every one as such
would make ordinary log reading impossible. And an error's extra and tags are still governed by
errors:read alone — if you put a prompt there, errors:read reads it.
One route to customer content remains open by design. If you use email addresses as customer identifiers, those identifiers are part of the identity spine and appear wherever identifiers do — on traces, logs, errors and every timeline. No scope withholds them, because they are the join key rather than a field. If a credential must not see them, identify customers by an opaque id.
Scope keys accordingly: give a key the metadata-only counterpart, and add the content scope only when it genuinely needs verbatim customer content.
Your project's content policy also binds everyone
Permissions decide who may see recorded content. The content policy
decides whether that content may be sent to a model at all — at the judge_transfer boundary,
which a typed evaluation crosses. Content the policy does not permit is never sent, however broadly
the caller's key is scoped, and the evaluation records that it was withheld by policy. Withheld by
credential and withheld by policy are different facts: re-minting a key changes the first, and only
a policy change alters the second.
Every project starts with a policy that permits what the product already did, so this changes nothing until somebody edits it.
What the viewer role can see
A viewer lacks four reads a member holds: the three content scopes and warehouse:read.
Other differences are writes, deletes or administrative actions. The content restrictions
apply to every surface:
A viewer keeps every metric, log, trace and error; dashboards, alerts, incidents and on-call; the customer and account lists, activity timelines and per-signal counts; the recording list; and agent-run summaries and LLM spend.
A viewer cannot play back a session recording or see the errors and events captured during it, open an agent run's transcript or the written explanation on its automated quality scores, read a customer's profile properties, or search customers by a profile property such as a name. Where a name would have labelled a row, a viewer sees the raw identifier instead.
If someone in the viewer role needs any of that, change their role to member — the scopes are not grantable separately. A key can only receive scopes its creator holds, so a key created by a viewer can never carry one of the three.
Response and collaboration
| Resource | Scopes |
|---|---|
| Alerts | alerts:read, alerts:acknowledge, alerts:resolve, alerts:rules:read, alerts:rules:write, alerts:rules:delete |
| Silences | silences:read, silences:write, silences:delete |
| Delivery channels | channels:read, channels:write, channels:delete |
| Agent event destinations | event_destinations:read, event_destinations:write (write is owner or admin only) |
| On-call | oncall:read, oncall:contacts:write, oncall:write, oncall:delete |
| Escalation | escalation:read, escalation:write, escalation:delete |
| Incidents | incidents:read, incidents:respond, incidents:write, incidents:delete |
| External monitors | monitoring:read, monitoring:write, monitoring:delete (write and delete are owner or admin only) |
| Status pages | status:read, status:write, status:publish (publish is owner or admin only) |
| Product experiments | experiments:read, experiments:write, experiments:launch (launch is owner or admin only) |
| Dashboards | dashboard:read, dashboard:write, dashboard:delete |
| Live screens | live:read, live:write, live:publish |
| Connections and tickets | connections:read, connections:write, connections:delete, tickets:read, tickets:write, tickets:delete |
| Account segments | account_segments:read, account_segments:write, account_segments:delete |
monitoring:read lists monitors, their configuration, job history, audit and diagnostics; it is
viewer-granted, never returns a secret value, and is also required by every monitor write. monitoring:write creates, edits, pauses, resumes
and test-runs monitors and monitoring:delete deletes them. Because every monitor pages through its
own alert rule, creating, resuming or re-routing a monitor also requires alerts:rules:write, and
deleting one also requires alerts:rules:delete. A header that sends a stored credential also needs
connections:read to reference it. See External monitoring.
status:read lists status pages, their drafts, publication history and the preview of the public
document; it is viewer-granted. status:write edits drafts — components, public incidents,
maintenance — and makes nothing public; members hold it. status:publish publishes a draft,
unpublishes a page and (with status:write) deletes one; it is owner or admin only and does not end in
:read, so a key reduced to its read permissions loses it. Associating a component with a monitor
also needs monitoring:read; associating it with a service, or linking a public incident to an
internal one, also needs incidents:read. See Status pages.
experiments:read lists product experiments, previews launch admission and reads per-arm counts;
it is viewer-granted. experiments:write creates drafts and edits a draft at its current revision;
members hold it. experiments:launch launches a draft and stops a draft or a running experiment,
and is required to create an application key; it is owner or admin only. Assigning people, registering
exposures and recording outcomes needs experiments:deliver, which no role and no ordinary key can
hold: it exists only on an application key, a project-bound secret for your application
server. Never put an application key in browser or mobile code. See
Product experiments.
flags:evaluate returns decisions for the flag keys a caller names and nothing else: it lists no
flag, reads no definition, targeting or local-evaluation snapshot and changes nothing. Every role
holds it, so an agent may use it with its own key or connection, and it is one of the two scopes a
client delivery key (purpose: client_delivery) can carry, beside surveys:respond. A client
delivery key is a public, project-bound key for your page or app: it holds flags:evaluate,
surveys:respond or both, chosen explicitly, never any other scope, and its creator must hold
api_key:write and each scope it grants. flags:evaluate is the scope of POST /api/v1/flags/evaluate
(MCP read action evaluate_client_flags, CLI anectico flags evaluate); flags:read remains the scope
for definitions, the full decide route and the local-evaluation snapshot, and never stands in for
flags:evaluate. See Authentication.
app_views:share decides what leaves your organization: it creates, lists and revokes an app-view
grant, which lets a page you host show one or more saved trend results to its visitors through a
short-lived browser token. No reading permission implies it, a viewer or member never holds it, and an
agent connected over OAuth can never hold it at any role. Creating a grant also needs api_key:write,
api_key:read, analytics:read, persons:read and every permission the shared results were measured
with; listing needs api_key:read; revoking needs api_key:write. app_views:mint is the other half:
only the key a grant names as its data source may turn that grant into a browser token, and that key also
needs analytics:read, persons:read and the results' own permissions — app_views:mint alone reads
nothing and shares nothing. Sharing is also off until your project's
content policy allows it.
See Authentication.
Responder actions such as acknowledging an alert or appending to an incident are separate from configuration and deletion. Give on-call automation the responder scopes it needs, not blanket administration.
For event delivery to your agent, event_destinations:read
reads owned destinations and recent attempts. It never returns a signing secret.
event_destinations:write creates and manages destinations and sends a signed test.
Existing-destination controls also require the read scope. Creation requires a management
key created by a person. OAuth connections cannot own destinations. A key sees only its own
destinations; an interactive workspace owner or admin can inspect and control all keys.
The key also needs each subscribed subject's read scope. Every delivery checks that key's
current authority. Adding a scope later cannot widen a destination's original scope ceiling.
Treat channels:read as a credential-grade scope. Reading a delivery channel returns its stored
configuration as you saved it, and a webhook channel's configuration includes its URL and any
request headers you attached — which is where an authorization token usually lives. A key that only
needs to send telemetry should not carry it. Sending a test notification is channels:write, not
channels:read, because it delivers a real message to that channel's recipients; a key used for
test-sends needs both, since the test first reads the channel it is about to use.
Personal contact methods are self-service and are gated twice. oncall:read lists them and
oncall:contacts:write adds, edits, or removes them; separately, every request is confined to the
calling user's own contact methods, so no key can read or change someone else's. Both scopes are
available to every role, including viewers — being reachable when you are on a rotation does not
depend on your role. Note that an API key acts as the member who created it, so a key you did not
intend to use for this still needs oncall:contacts:write withheld to be unable to change that
member's paging destinations. Schedule administration stays on oncall:write and oncall:delete.
For dashboards, dashboard:write permits creation, cloning, and widget or layout changes. Changing
dashboard metadata or variables is limited to the dashboard owner and organization admins, and
deletion requires both dashboard:delete and dashboard-owner or organization-admin status.
dashboard:read never makes a shared URL public; dashboard links still require an authenticated
same-organization principal.
Live screens are the one place a link is deliberately the credential, so their three scopes are
graded further apart than they look. live:read views a screen and its published state, and
live:write creates and edits one — adding, removing and reordering panels, and setting the labels
on them. Neither discloses anything to anyone outside your organization: editing changes what a
display would show.
live:publish is the separate decision. It mints the viewing link for a published screen, and
rotates, pauses, resumes and revokes it. Anyone holding that link sees the screen without signing
in, which is the point of it — a wall display has nobody to sign in — so give live:publish only to
the credential you intend to publish with, and withhold it from keys that merely build screens. A
published screen keeps running on the publishing key's current permissions, rechecked on every
refresh: revoking that key, letting it expire, or narrowing its scopes stops the display. That is
also why live:publish is not a :read scope even though what it shows is reads — a key reduced to
its read permissions must lose the ability to keep serving a public display, and it does.
An organization member or developer can hold all three; a viewer can hold live:read only. An
agent connected over OAuth can read Live screens and can never publish one, at any role, because a
viewing link outlives the connection that created it.
See Put a project on a live screen for the workflow and Live viewing links and access for what a link is bound to and what stops it.
Organization, AI, and agents
| Resource | Scopes |
|---|---|
| Settings | settings:read, settings:write, settings:delete |
| GitHub configuration | github_config:read, github_config:write |
| Projects and members | projects:read, projects:write, projects:delete, members:read, members:write |
| API keys | api_key:read, api_key:write |
| Sharing saved results with your own pages | app_views:share, app_views:mint (owner or admin only) |
| Audit trail | audit:read (owner or admin only) |
| Agent activity, and which proof pages people opened | activity:read (your own agents; counts of opens); add audit:read to see the whole workspace and who opened |
| Scores and evaluations | scores:read, scores:write, evals:read, evals:write |
| Evaluation explanations | agents:content:read (additionally reveals the explanation field on an evaluation result) |
| Usage | usage:read |
| Billing plans and subscription status | billing:read |
| Checkout, payment portal and plan changes | billing:write (organization-wide credentials only; checkout also uses billing:read) |
| Family policy changes | usage:read + organization-wide settings:write |
| MCP | mcp:read, mcp:write |
| Documentation | docs:read |
| Agent containment | quarantine:request, quarantine:approve (owner or admin only) |
| Content governance | governance:read, governance:write (write is owner or admin only) |
Family policy reads use usage:read; a project-bound key receives the same
organization policy and usage through its exact project mirror. Updating the
retention choices and allocation split also requires organization-wide
settings:write. MCP and the CLI mutation add mcp:write and preview and confirmation.
Customer credentials cannot grant larger retention ceilings or a larger pool.
Settings permissions are deliberately separate. settings:read shows stored organization and
global LLM pricing overrides without edit controls; it does not expose the embedded fallback
catalog. Adding or editing an organization override requires settings:write, and deleting one
requires settings:delete.
The same three scopes cover the organization record and a project's first-application setup draft.
Reading the organization or a setup draft requires settings:read; renaming the organization or
saving a setup draft requires settings:write; deleting the organization requires
settings:delete. A key holding none of them authenticates normally and receives
403 PermissionDenied on all of them — which is the point: an unscoped key must not be able to
delete your organization. Changing a plan or a usage limit is not available to any organization
role, including owner.
Listing projects and listing the member roster are reads in their own right: projects:read returns
each project's name, slug, id, and retention — whether you list them all or fetch one by id — and
some endpoints need it indirectly, because exporting data validates the project you name before it
accepts the job. members:read returns the roster including pending
invitations, which are the not-yet-accepted half of the same list. Both are available to every role,
because the project selector and the member picker appear throughout the product; creating,
renaming, or deleting a project, and administering members or invitations, remain owner or admin only
under projects:write, projects:delete, and members:write. A key without the read scopes
authenticates normally and receives 403 PermissionDenied on those endpoints, so an ingestion-only
credential cannot enumerate your projects or your people.
Key management is itself scoped. Listing the organization's keys requires api_key:read, and
creating, renaming, revoking, or re-scoping one requires api_key:write. A key without them
authenticates normally but receives 403 PermissionDenied on those endpoints — listing exposes
every key's id, name, and granted scopes, so it is not something an unscoped credential should
reach. Granting api_key:write does not widen a key: a new key can still only receive scopes its
creator already holds. Nor is it a way around a containment: a key frozen by an approved quarantine
refuses a scope change until the quarantine is lifted, and lifting takes the owner or admin
quarantine:approve. Leave both off any key that does not administer credentials.
audit:read opens the organization audit trail in the Console (Audit log) and through the account audit endpoint. It
can be filtered by resource, action, and result; it omits network identifiers, user agents, and
arbitrary metadata. Owners and admins receive it through their role, while narrower keys must name
it explicitly.
activity:read opens the agent activity record: every call an agent
made to Anectico, with its outcome. Every role holds it, and an OAuth connection carries it. With
this scope alone you see the activity of your own keys and connections. An API key or a connection
sees only its own calls. To see every agent in the workspace you also need audit:read, which
only owners and admins hold and which a connection can never carry. A row shows the id of an
object only to a reader who holds the read scope for that kind of object.
activity:read also opens the count of proof pages people opened.
The counts name nobody. audit:read as well shows who opened. A row shows the id of an opened object
only to a reader who holds the read scope for that kind of object.
mcp:write is an umbrella gate. An MCP write also requires its domain scope. For example, creating
an incident requires both MCP write access and the incident permission enforced by that tool.
Exact telemetry deletion requires mcp:write plus traces:delete or logs:delete on a
project-scoped key; the MCP actions do not accept a project override.
Containing a misbehaving agent takes two people. quarantine:request lets any member or developer
ask for an asset, an observation key, or a single API key to be frozen, revoked, or both — asking
enforces nothing on its own. quarantine:approve is owner or admin only, and it is what actually
disables the credential: approving is what signs and enforces the request, and lifting a
containment afterward takes the same scope. No key can hold only quarantine:approve — every role
that grants it also grants quarantine:request — but the platform separately refuses to let one
principal both request and approve the same containment, so a key that could do both still cannot
approve its own ask.
Withdrawing a request nobody approved is the exception, and it sits with quarantine:request:
retracting your own ask hands capability back to nobody, because a pending request never touched a
credential. Retracting somebody else's takes quarantine:approve — it suppresses a containment
before a second person can consider it. See
Revoke an agent's access for the full
request-approve-lift flow and
Withdraw a request nobody approved.
Your project's content policy
Your project's content policy says which kinds of recorded content may cross which boundary:
model transcripts, tool arguments and results, session-replay recordings, the property bag on a
person, the separate property bag on a business account or group, and agent memory — each against
being stored at all, served to a reader, included in a bulk export, or sent to an evaluation model.
governance:read returns that grid for one project or for
every project in the organization. It is available to every role, including viewers, because the grid
contains no recorded content of any kind — only the decisions and the version — and the people a
disclosure control protects should be able to check it.
governance:write replaces a project's policy and is owner or admin only. It is the broadest single
change the API offers: widening one cell makes every transcript in that project readable,
exportable or shippable to an evaluation model, retroactively, for every key that already holds the
matching content scope. Leave it off any key that does not administer disclosure policy.
Two things are worth knowing before you rely on it. Every project already has a policy, including one you have never opened: the organization seeds each project with a grid matching what the product does today. A project whose grid has not been written yet reads back as fully denied rather than as an error — that is the shape of the response, not what the boundaries do in the meantime, which is covered below. And a change takes up to 30 seconds to take effect everywhere, so a tightening is not instantaneous.
The four boundaries are not equally reversible, and one of them is permanent. Refusing a class at the reading, exporting or evaluation boundary withholds a copy of something Anectico still holds, so widening the policy and asking again returns it. Refusing a class at the storing boundary means the content is never written down: the observation is kept — timing, status, model, token counts, cost, and the person it belongs to — and the bodies are not, for good. Widening applies forwards only and cannot recover what was recorded while the class was refused. The 30-second propagation therefore cuts both ways at that boundary: a tightening leaves up to 30 seconds of content recorded (which you can delete), and a widening leaves up to 30 seconds of content unrecorded (which you cannot recover).
At the reading boundary, "we do not know yet" means withhold — the opposite of storing. If Anectico briefly cannot read your project's policy, reads return the observation with the content left out rather than guessing that you would have allowed it. Nothing is lost: the content is still stored, and the same request returns it once the policy can be read again. The reason the two boundaries answer the same uncertainty in opposite directions is the reversibility above — a read you have to repeat costs a retry, and content disclosed against a refusal you had just recorded cannot be taken back. The evaluation boundary answers this uncertainty the same way reading does — if the policy cannot be read, nothing is sent to a model — because its destination is outside Anectico and outside your organization, which is the one place a mistaken send is worst.
A project whose policy has not been set up yet is treated as if it already had the standard one, at all four boundaries. A brand-new project gets its policy from a sweep that runs every fifteen minutes rather than at the moment you create it, so there is a short window before its grid exists. In that window the project behaves exactly as it will once the grid is written — which is the point: the alternative answers the same project two different ways depending on which side of the sweep a request lands, and records a refusal against a preference you had not expressed. Nothing is decided differently because of it; the standard posture is what your project is about to be given, and you can tighten it the moment it exists.
Exporting was the last boundary to work this way, and the change is visible on the export itself: a job decided this way is marked as having used the standard posture rather than a grid you set, so a file assembled before your project's grid existed is never mistaken for one you had approved.
An export that covers a whole organization rather than one project is the exception, and still leaves recorded content out. There is no single grid to apply — the projects it spans may each have set a different one — so nothing is assumed on their behalf. Scope the export to a project to get its content.
Reading, exporting, and automatic evaluation are independent boundaries. A class set to deny
at read and allow at export can still be exported, and the same combination with
judge_transfer can still be used by an automatic quality check. Each operation asks the cell for
the boundary it is actually crossing. Denying any one cell affects only operations that cross that
boundary.
A read that covers your whole organization is governed row by row. A content policy belongs to a
project, and a request that names no project can return rows from projects whose policies disagree.
Each row is answered by the policy of the project it came from — so one organization-wide search of
traces or logs returns content from the projects that permit it and leaves it out for the projects
that refuse, in the same response, and says which rows were affected. This is what you get from the
API, the MCP endpoint and the CLI when you do not pass project_id.
Two things still come back without content, and they are fixed in different places. Trace exemplars
on a metric point carry no project of their own, so a metric read that names no project cannot obey
a policy and leaves their attributes out — name the project (project_id) and the content comes
back. And a row that carries no project at all, because the telemetry was sent with an
organization-level credential rather than a project one, has no policy that could govern it whichever
way you read it; sending that telemetry with a project-scoped credential is what fixes that one, not
the read. A proof page always names a project for you.
At the storing boundary, "we do not know yet" means record. A project whose policy has not been set up yet, and a project whose policy Anectico briefly cannot read, both keep recording on the standard posture rather than being refused. A refusal you have already configured is not affected — the last policy read for your project stays in force through any outage of ours. The reasoning is the asymmetry above: content recorded in error can be deleted afterwards, and content never recorded cannot be brought back.
Session-replay recordings and person property bags are now enforced at the storing and reading boundaries too. Every class in the grid except agent memory now binds at every boundary where it has a flow, and the two that recently did not behave like this:
- Session replay at
store. A recording chunk is refused outright — no recording appears, and the upload is answered with an error naming the content policy rather than being accepted and thrown away. What a session was linked to is unaffected: the errors and product events captured during a session, and a recording's existence, length and person, are not the recording itself and are never withheld under this rule. - Session replay at
read. Playback is refused with an error rather than an empty timeline, because an empty timeline is indistinguishable from a session that recorded nothing. The decision follows the recording's own project, so an organization-wide credential gets the right answer without naming one. - Person properties at
store. The property values on anidentify()call are not written down; the identity itself still is. Anectico still learns who the person is, still merges an anonymous visitor into an identified one, and still links their sessions, errors and traces — only the property values are dropped, and the response says so. Clearing a property (unset) is never refused: it removes recorded content rather than recording any. - Person properties at
read. The property bag is left off a person, per person, according to the policy of the project that person belongs to — so one organization-wide list can return properties for the projects that permit them and leave them out for the projects that do not, in the same response. The record says which control removed them, because only one of the two is something you can fix with a different key.
The storing boundary also reaches the context you attach to an error. Tags, extra data and
captured request headers on an error report are your own key and value bags, so a field you name after
a model-content convention — extra.gen_ai.prompt.0.content, say — holds the same bytes the policy
governs on a span, and is treated the same way. A refusal drops those fields from the error and from
its issue, and names them in anectico.content.not_stored; every other field you sent is untouched.
Event properties are governed at BOTH boundaries. The property bag on a product event is your own key and value bag, so a property named after a model-content convention is the same bytes the policy governs on a span. At the storing boundary those properties are not written down. At the reading boundary — a customer's activity timeline, or an account's — they are left off the event and the response names them, per event, according to the policy of the project that event belongs to. So one organization-wide timeline can return properties for the projects that permit them and leave them out for the projects that do not, in the same response. Every other property you sent is untouched, and so is the event itself: its name, its time, the person and the session it belongs to.
A promoted evaluation case is refused rather than trimmed. When you promote a production agent
run into a dataset, the run's own recorded content is governed as usual — refused classes are left
out of the frozen case and the case records what went. The expected and metadata documents you
send with it are treated differently: if one of them carries a field named after a model-content
convention that your policy refuses to store, the whole promotion is refused and nothing is
written. The response names the document and the field so you can remove it and retry.
The reason is that a dataset version cannot be edited. Once written, its cases are permanent — that is what makes an evaluation result citing a version mean something. Quietly dropping a field from an expected answer would leave a permanent, unremovable specification you did not write, scoring differently for ever, and nothing in the record would say so. Refusing costs you one edit; you are still holding the document.
Reading a dataset is governed too, and separately: a case's frozen run keeps its shape with the
recorded bodies removed, while expected and metadata are withheld whole and replaced by a note
saying which field was refused and whether it was your policy or your key that refused it. That
matters because the two boundaries are separate settings — a project may record content and refuse
to hand it back — and because a policy you tighten today applies to cases promoted before you
tightened it.
Two consequences are worth knowing before you set either to deny:
- People search stops matching on property values for a project that refuses them at
read. It has to: leaving the search able to match and rank on a value it will not print would answer the question the withholding exists to refuse. Searching by any identifier you sent withidentify()keeps working. A people search that names no project takes the same narrower behavior whatever your policy says, because the search has to be built before it knows which projects the results will come from — name a project to keep property matching. - Trace and log search stops matching on withheld attribute values, for the same reason.
Free-text search keeps working over the parts it may still show you — the message, service and
operation names, and identifiers — so most searches are unaffected. Searching for an exact value
of a withheld attribute is refused with
400rather than quietly answered from a narrower set, because a filtered result you cannot tell apart from an unfiltered one is worse than an error. This applies to trace search, log search, live tail, unified search and correlation. As with people search, a request that names no project takes the narrower behavior whatever your policy says — name a project to keep attribute matching. - Property-based audiences stop refreshing for a project that refuses person properties at
read, because an audience defined by a property value is computed by reading those values. Audiences defined by behavior are unaffected.
Agent memory has no producer at all yet, so its four cells govern nothing whichever way you set
them; that is stated rather than hidden, because a control you believe in and do not have is worse
than one we do not offer. Note that documents retrieved by a RAG or search step are recorded as part
of the model transcript and are governed by that class, not by agent memory. Session replays and
person profiles are still also governed by their own permissions (replay:content:read,
persons:profile:read); the two controls compose, and neither stands in for the other.
Records and responses affected this way say so, and the three statements are deliberately different:
| Field on the record | What it means | What changes it |
|---|---|---|
anectico.content.not_stored |
The content was never written down, because your policy refused that class at the storing boundary | Nothing — this is permanent. Widening the policy applies forwards only |
anectico.content.policy_withheld |
The content exists and your project's policy refuses that class at this boundary | A change to the content policy |
anectico.content.withheld |
The content exists, your policy permits it, and this credential is not in the sensitive-content tier | A key with the matching content scope |
Where both a policy refusal and a credential gap apply to the same field, the policy is what is
reported, because a wider key would not have helped. All three fields are written by Anectico and
only by Anectico: they are stripped from anything you send — span and log attributes, product-event
properties, and error tags, extra data and captured headers alike — and re-issued from what was
actually removed, so they cannot be forged into your own telemetry. anectico.content.* is a
reserved key prefix for that reason; a key of your own with one of those three names is dropped
rather than stored, whatever your policy permits. See
Prompt and completion content.
Reading the documentation from an agent
docs:read lets a key read Anectico's own product documentation — these pages, byte for byte —
through the search_docs and get_doc actions on the MCP endpoint, and through the docs source
on search and the doc reference kind on fetch. It is the one scope on the surface that
discloses nothing about your organization: the pages it returns are published at
anectico.com/docs and are the same for everyone.
It is available to every role, including viewers, for that reason. It exists as a scope so a key can deliberately leave it out — a narrow ingest or cleanup key has no use for the manual — not because the content needs protecting. Give it to any agent you expect to configure something: an agent that can look up a scope name, an alert expression or an SDK call is an agent that stops guessing them.
Safe key patterns
- Browser or mobile SDK: project-scoped
ingest:writeplusanalytics:write; addreplay:writeonly when recording browser replay. Flag evaluation from a page or app uses a separate client delivery key with onlyflags:evaluate; never aflags:readkey, which reads every definition. - Server SDK: project-scoped
ingest:write, addinganalytics:writewhen it calls identify, group, screen, or diagnostic-event methods. - OpenTelemetry collector: project-scoped
ingest:writeonly. - Any agent you expect to configure something: add
docs:readso it can look up a scope name, an alert expression or an SDK call instead of guessing one. It reads published documentation and nothing about your organization. - Read-only agent:
mcp:readand only the read domains it needs.services:read,agents:read, andllm:readare each sufficient on their own for service health, agent-run summaries, and spend — a read-only agent does not needtraces:readfor them. Addpersons:profile:read,replay:content:read, oragents:content:readonly when the agent must read customer content verbatim; withholding them is enough to withhold that content, including the prompts and completions third-party AI instrumentation records as span attributes.traces:readon its own now returns those spans with the bodies removed and ananectico.content.withheldattribute naming what was removed, so an agent can investigate latency and errors on traces without ever reaching a transcript. Your project's content policy can withhold the same bodies from a key that does hold the content scope; those responses carryanectico.content.policy_withheldinstead. - App you host that reads Anectico (a status board, a support page, a bot, a nightly report): one
dedicated management key for each deployed app, bound to one project, with a fixed expiry and only
the read permissions the app calls. A service-health board holds
services:readand nothing else: notmcp:read, notapi_key:write, nothing for billing, members, connections or privacy, and no bundle. Addpersons:profile:read,replay:content:readoragents:content:readonly when the app must show that content. The key identifies the app, so your backend decides what each of its own users may see. The agent that creates the key needsapi_key:readandapi_key:write(andmcp:writefor the preview and confirmation) beside every permission it grants; the app's runtime key holds none of them. Stop the app by revoking the key: a freeze removes write permissions only, so a key that only reads keeps reading. See Which credential each host holds. The worked examples hold: a support pagepersons:read; an on-call botoncall:read,projects:readandmembers:read; a nightly issue reporterrors:read, plusinsights:read,analytics:read,persons:readand the result's source permissions when it reads a saved analytics result. See Give an app its own key. - Telemetry-cleanup agent: dedicated project key with
mcp:writeand onlytraces:deleteorlogs:delete; both actions preview the signed project and exact target before confirmation. - CI symbol upload: dedicated project key with
errors:write; addreleases:writewhen the same job also registers a release. - Release pipeline registering agent inventory: dedicated project key with
agents:write, minted by an owner or admin. It covers the deployment, the effective configuration and the bill of materials — all three legs of one declaration. Addagents:readonly if the same job reads the registration back or checks the review queue it feeds; the write does not admit any read on its own. - Operations automation: explicit responder or configuration scopes, fixed project, no unrelated deletes.
The wildcard platform-admin permission is not grantable to API keys.
persons:correct permits an audited alias separation with a current preview revision. Granted to owners or admins only; API keys may request it explicitly. persons:read permits preview and mapping history without profile property values. SDK analytics:write does not permit corrections.
persons:export permits exporting a person's data: requesting the archive of everything held about one person, reading the export and downloading it. Granted to owners or admins only; API keys may request it explicitly, and a project-scoped key is confined to its own project. It is not enough by itself: the credential must also hold every permission that reads the person's data (persons:read, persons:profile:read, analytics:read, errors:read, logs:read, traces:read, agents:read, agents:content:read, experiments:read, groups:read, replay:read and surveys:read), and a credential missing one is refused, so an archive never silently lacks a kind of record. Losing any of them stops downloads of archives already made. Diagnostic sampling requests and grants use persons:read; submitted problem reports use errors:read and agents:content:read, and their text and route also follow the project's export content policy.
Survey receipts use surveys:read and agents:content:read. Their caller-chosen display, response or dismissal identifiers are content: they leave only when the project's export policy permits both model_transcript and tool_arguments, just as for survey answers. Denied or unreadable policies withhold the identifiers with the usual content-withheld marker. Survey offers have no caller-chosen identifier and use surveys:read.
persons:erase permits erasing a person — removing their profile, identity, product events, errors, logs, traces and agent events and permanently blocking their identifiers — and reading erasure operations. Granted to owners or admins only; API keys may request it explicitly, and a project-scoped key is confined to its own project. Reading an operation takes this same permission, not persons:read, because an operation shows the reason its requester typed. It is irreversible, and persons:read, persons:correct and errors:delete do not admit it.
groups:erase is the owner or admin permission for irreversible account erasure and reading its private operation records. API keys may request it explicitly; a project-scoped key is confined to its own project. The default keeps the account's people and their events. Reading an operation takes this permission because it includes the requester and their reason. Account erasure is available from Privacy → Requests, REST, confirmed MCP and the CLI. Opting to erase members also requires persons:erase. groups:delete remains the reversible removal of an account definition after its current members have been detached.
Personal connection permission
oauth:connections lets you list and revoke the agent connections you
approved. Every workspace role, including viewer, receives this personal permission.
Use Agents and keys in the Console or anectico auth connections list|revoke. This permission cannot be put on an API key or
given to a connected agent, and it is never offered in the key picker or the Full access preset.
Oversight of other people's connections additionally requires members:write; use Everyone's connections or --org-wide in the current workspace.
Saved findings use findings:read (viewers and every member) and findings:write (members and
above). Agent keys receive write only when explicitly granted; the default investigate key bundle
writes nothing. Create and revise also need projects:read and each reference's source read permission.
MCP writes need findings:read for preview, mcp:read and mcp:write for gateway confirmation.
Reading a finding deliberately shares the author's captured snapshot with teammates and needs only
findings:read; following a live source link still needs that source's permission. Person capture
has only an id, no profile. OAuth investigation can read; saving needs a scoped write key. See
Save a finding.
Agent budgets
Budget and accepted-usage reads need activity:read for this credential, or audit:read for another credential and workspace defaults. Setting limits needs api_key:write plus owner or admin; a connection also needs members:write. The default investigate and incident bundles include the own-read scope. Budget checks always apply, even to a key without permission to read them.
The request rate (how fast a key may send requests) is part of the same policy and follows the same permissions: reading it needs activity:read for your own key and audit:read for another key or the workspace, and changing it needs api_key:write plus an owner or admin. An ordinary scoped key, even one holding api_key:write, cannot change a request rate, its own or the workspace's. No new permission exists for it, and nothing a key can do widens its own allowance.
Observed screen coverage (get_observed_coverage and compare_coverage) needs
analytics:read and agents:content:read, and the project content policy must permit
its properties. Without either content control the whole screen kind is withheld:
no names, counts or observation-based absence are returned. Declarations remain
customer-authored configuration under tracking-plan read permission. Friction
actions and survey response properties follow the same content gate before their
counts or grouping are read. Fix measurements that correlate operation properties
also require the content scope and policy.
Write outcome recovery
lookup_write_outcome requires activity:read and the original write's current permissions and
authority, including mcp:read and mcp:write for MCP. It is scoped to the credential that wrote.
See Write recovery for selectors and retained outcome states.
Reserved action scopes
These scope names are defined, but no current customer operation uses them. Granting one does not enable an action feature.
| Scope | Roles that hold it |
|---|---|
actions:read |
Owner, admin, member, developer, viewer |
actions:write |
Owner, admin, member, developer |
actions:delete |
Owner, admin |
Technical asset declarations
register_agent_asset requires agents:write and agents:read for preparation,
exact shadow adoption, replacement and original-result disclosure. MCP also needs
mcp:write and mcp:read. Confirmation binds current state and grants no missing
permission. The same stable key is bound to the committing operator; it cannot be
reused by another operator or body. See
asset declarations.
Browser aggregate sharing
Provisioning requires app_views:share, api_key:read, api_key:write, analytics:read, persons:read
and every owner-derived source scope; MCP adds its read or write umbrella. Runtime mint uses the exact
parent project key with app_views:mint plus all base and source scopes. OAuth does not authorize either.
Ordinary read, wrong-project and mint-only credentials cannot control grants.
See Share a saved aggregate with a browser page for the full credential, theft, lifetime, privacy and external-copy contract.
Deliberately public static aggregates
Static publication uses the same explicit
app_views:share, key-control and source permissions as grant creation. The
parent still needs app_views:mint and every data permission. A public page is
external sharing under the default-denied app-view policy; reads alone never
authorize it. Confirm publishable_result_id only when anyone possessing the
credential may see that one aggregate. No new scope or viewer identity is implied.
Account objective authority
Fresh actual-policy account burn and immutable original-trigger evidence both require
alerts:read, groups:read, traces:read and agents:content:read, with current project policy;
MCP adds mcp:read. Customer API keys and verified member sessions are supported. External OAuth,
operator and SYSTEM source actors refuse before evidence assembly. Scheduled API-key or member
admission is separate from current read authority. Authoring, work edits and a resume proposal need alerts:rules:write and the three source grants; MCP adds mcp:write. Existing generic
validation also advertises evals:read. Pause-only needs rule-write authority without a source
read, and deletion uses existing alerts:rules:delete. Ordinary mixed rule lists retain permitted
controls while withholding source fields and identifying text with field-specific reasons.
Account request objectives defines the current activation
refusal; possessing these scopes does not make a proposal active.
Exact financial job grants
Use the account financial authority table above. In particular,
get_account_business_write_result is a read under the original actor; replaying a declaration
mutation still requires its write authority. The
declaration workflow owns the distinction.
Support story reads
persons:read admits a requester story. errors:read reveals errors and recorded contacts, traces:read reveals qualifying observed server operations, and replay:read reveals recording metadata. Missing optional grants withhold only their source. Complaint-ID selection itself requires both errors:read and persons:read. MCP additionally requires mcp:read; deeper technical content and playback retain their own current gates.
Optional support steps
Read a requester story with persons:read and MCP mcp:read; add errors:read, traces:read and replay:read only for selected sources. Technical content remains separately governed by agents:content:read; replay content follow-ups retain replay:content:read. Record preview, confirmed apply and owning replay require errors:write, errors:read and persons:read. Get and list require both reads; withdrawal requires errors:delete and both reads. MCP mutations additionally need mcp:write. Neither ingest:write nor tickets:write substitutes; no write authority is added to a read OAuth session.