Skip to content
Console
Browse documentation
Reference

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:read lists 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 need replay: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 need replay:content:read too. See Read the session as steps.
  • The masked page copies a heatmap is drawn over follow the same split: replay:read lists which pages have one, replay:content:read reads the copy itself (without it the heatmap shows its abstract page), replay:delete deletes one, and the browser SDK uploads them with the replay:write key it uses for recordings.
  • agents:read gives a run's summary — agent, model, steps, tools, duration, tokens, cost, outcome. The run's transcript needs agents:content:read.
  • persons:read gives the identity spine — who exists, their aliases, their activity counts and timeline. It also admits find_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_people works the same way: persons:read admits 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 needs errors:read and persons:read; it does not need content permission and never implies complete external support coverage. Their profile properties need persons: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 fingerprint and group_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_email still 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 0 when 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:read and persons: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:read or groups:read and 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 an identify() 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 with identify() 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 400 rather 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:write plus analytics:write; add replay:write only when recording browser replay. Flag evaluation from a page or app uses a separate client delivery key with only flags:evaluate; never a flags:read key, which reads every definition.
  • Server SDK: project-scoped ingest:write, adding analytics:write when it calls identify, group, screen, or diagnostic-event methods.
  • OpenTelemetry collector: project-scoped ingest:write only.
  • Any agent you expect to configure something: add docs:read so 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:read and only the read domains it needs. services:read, agents:read, and llm:read are each sufficient on their own for service health, agent-run summaries, and spend — a read-only agent does not need traces:read for them. Add persons:profile:read, replay:content:read, or agents:content:read only 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:read on its own now returns those spans with the bodies removed and an anectico.content.withheld attribute 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 carry anectico.content.policy_withheld instead.
  • 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:read and nothing else: not mcp:read, not api_key:write, nothing for billing, members, connections or privacy, and no bundle. Add persons:profile:read, replay:content:read or agents:content:read only 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 needs api_key:read and api_key:write (and mcp:write for 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 page persons:read; an on-call bot oncall:read, projects:read and members:read; a nightly issue report errors:read, plus insights:read, analytics:read, persons:read and 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:write and only traces:delete or logs:delete; both actions preview the signed project and exact target before confirmation.
  • CI symbol upload: dedicated project key with errors:write; add releases:write when 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. Add agents:read only 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.