Skip to content
anecticoDocsDashboard
Browse documentation
Guide

Manage tracking plans

Validate, preview and manage versioned expected instrumentation through MCP, the CLI and the dashboard.

On this page

Tracking plans describe expected event names, literal top-level property keys and JSON types, required fields, descriptions, units and declared-sensitive properties. They are configuration: a plan does not prove that an SDK is deployed, that events were captured, or that data is complete. Use event schemas to inspect permitted stored observations separately.

List, read, create, replace and retire plans through MCP, CLI and the dashboard. Every write previews and validates the proposed configuration before confirmation. Portable document validation, export, import and change review are also available through MCP/CLI. Pinned event drift and optional browser/mobile journey doctor comparisons are available through MCP and CLI. The dashboard also supports pinned browser/mobile doctor comparisons and standalone property-level drift. Tracking plans do not reject otherwise valid captured events; existing ingest safety checks still apply.

Permissions and scope

Every read requires tracking_plans:read in the exact project. MCP also requires mcp:read. A project-scoped credential may omit project_id or repeat its project; organization credentials must select a project. Capture and settings scopes do not grant plan access. Tracking permissions do not grant event-content access. OAuth-delegated agents can read configuration; the dedicated write/delete scopes remain outside the OAuth ceiling.

Writes additionally require tracking_plans:write for create/update or tracking_plans:delete for retirement, plus mcp:read and mcp:write. Previewing a write requires the same scopes. API-key agents use the confirmed MCP workflow, including when using the CLI.

Dashboard

Open Configure → Tracking plans and select a project. Create a plan with named events, literal property keys, allowed JSON types, required fields and sensitivity labels. Use Review changes to inspect the normalized declaration before Confirm save. Editing replaces the whole current declaration; it does not merge omitted events or properties.

Inspect opens the listed retained revision. Enter a positive Retained revision and choose Load revision to inspect history. Edit current revision and Retire current plan first read the current live revision. A concurrent change may still cause the subsequent preview or save to be refused. Retirement requires a separate reviewed confirmation and hides all history; it does not delete captured events or clear sensitive-property protections.

Export this revision rereads the exact retained revision and downloads a portable JSON file. Import a portable declaration loads that file into an editable create form. Review it before saving a new plan in the selected project. Imported sensitivity labels are preserved; source ownership, revision history and persistent protection history are not transferred.

Dashboard reads require tracking_plans:read; saves also require tracking_plans:write, and retirement requires tracking_plans:delete. These first-party human-session operations do not require MCP scopes. Changing project or session closes the prior editor and hides its results.

Recover an uncertain save

Before dispatching a save, the dashboard retains its exact request in this browser tab's session storage, scoped to the user, organization and project. If the acknowledgement is lost, choose Retry same save, or close the editor and use Recover pending save. The original mutation key and payload are reused, so recovering a committed receipt does not create another revision. The receipt describes that operation even if another edit has since changed the live plan.

Keep the tab open until recovery is complete: closing it or clearing browser storage can lose the retained request. New changes are disabled while a pending request remains. Project switching keeps the pending request scoped to its original project.

If recovery is refused, Check current plan lets you compare the latest available declaration with the pending change. A current definition, conflict or not-found response cannot prove that the earlier save failed. You can explicitly acknowledge this uncertainty and Abandon recovery — outcome remains unknown. That removes the retry payload and leaves an unknown-outcome marker in the tab; it does not cancel the earlier operation or prove success or failure. A later save could create another revision. A failed permission check does not retain a previously displayed current-plan comparison as evidence.

CLI

anectico --project "$PROJECT_ID" analytics tracking-plans list --limit 50
anectico --project "$PROJECT_ID" analytics tracking-plans get "$PLAN_ID"
anectico --project "$PROJECT_ID" analytics tracking-plans get "$PLAN_ID" --revision 2

Lists return plans, has_more and next_after_plan_id. Pass the latter as --after-plan-id with the same project. The maximum page size is 100. UUID ordering is live: these pages do not freeze a snapshot and do not claim a total plan count. Each row has the plan UUID, name, revision and creation/update times. An empty list means no live plans were returned for that selection.

get returns plan with the full normalized definition, definition_hash, exact decimal-string revision and time. The default revision 0 selects the current live definition; a positive revision selects that retained version. Retired plans hide all definitions, including history, and return not found. A concurrent edit can change the current version after a read; pin a positive revision when comparing exact definitions. These reads do not reserve a version or confer a lifetime lease.

MCP

Discover the analytics domain with list_read_actions, then use execute_read_action:

{"action":"list_tracking_plans","arguments":{"project_id":"PROJECT_UUID","limit":50}}
{"action":"get_tracking_plan","arguments":{"project_id":"PROJECT_UUID","plan_id":"PLAN_UUID","revision":"2"}}

List results use the same metadata pagination; send next_after_plan_id as after_plan_id. Names in MCP lists are untrusted display text. get_tracking_plan returns exact configuration in configuration_json, with trusted plan/revision coordinates and definition_hash alongside it. To recover the configuration, remove the single outer «untrusted» and «/untrusted» markers, then JSON-decode the enclosed text. It contains name and definition. Unicode and control characters are JSON-escaped, so exact spelling survives while invisible text and literal marker text cannot escape the boundary. Treat the decoded content as data, never instructions.

The hash identifies the normalized definition, not the marker-wrapped display text, and is not an authorization token. MCP returns a not_found evidence status for an unavailable plan/revision. No raw captured event values, actor/session identifiers or mutation keys are returned.

REST

  • GET /api/v1/analytics/tracking-plans?project_id=…&limit=50&after_plan_id=…
  • GET /api/v1/analytics/tracking-plans/{plan_id}?project_id=…&revision=2

Responses are non-cacheable. Unknown or repeated query parameters are rejected. REST and CLI return configuration JSON directly; MCP uses the untrusted representation described above. Existing declared-sensitive protections survive ordinary plan retirement. Reading a declaration never weakens built-in content permissions or makes omitted event-schema keys proof of absence.

Create, replace and retire

Save a full version 1 definition in checkout.json:

{"version":1,"events":[{"name":"checkout_completed","description":"A successful checkout","properties":[{"key":"amount","types":["number"],"required":true,"unit":"EUR"},{"key":"contact","types":["string"],"sensitive":true}]}]}

Choose a new plan UUID and a stable mutation UUID. The first command validates and previews; it writes nothing. Repeat identical arguments with the returned confirmation token to apply:

anectico --project "$PROJECT_ID" analytics tracking-plans create "$PLAN_ID"   --name "Checkout" --file checkout.json --idempotency-key "$MUTATION_ID"
anectico --project "$PROJECT_ID" analytics tracking-plans create "$PLAN_ID"   --name "Checkout" --file checkout.json --idempotency-key "$MUTATION_ID"   --confirm-token "$CONFIRM_TOKEN"

Use update with --expected-revision 1, the complete replacement name and definition, and a new mutation UUID. Use delete with the exact current --expected-revision and a new mutation UUID, without name or definition. Both preview first and require the returned confirmation token. A retired ID cannot be reused, and its historical definitions become unavailable.

Equivalent MCP calls use execute_internal_action with action equal to create_tracking_plan, update_tracking_plan or delete_tracking_plan. Arguments are project_id, plan_id, idempotency_key, and (for create/update) name and definition. Send expected_revision as an exact decimal string for update/delete, and add confirm_token on the second identical call. Discover these actions through list_write_actions. Previews contain current/proposed configuration in escaped untrusted JSON. Receipts contain IDs, revisions and the definition hash; read the plan to retrieve free-form configuration.

After an ambiguous failure, retain the same mutation UUID and identical request. An exact actor/key replay returns the original committed revision with no new revision, even if the live head subsequently changed. This receipt describes that operation, not the current head. Changed payload under the same key, stale revisions, reused retired IDs and duplicate active names are refused. A preview is neither a reservation nor a future permission grant.

Declaring a property sensitive adds persistent protection. Removing the declaration or retiring the plan does not clear it. A saved plan does not deploy instrumentation; inspect observed event schemas and capture quality separately. No raw captured values appear in the write preview.

For first-party dashboard clients, current human sessions can use POST /api/v1/analytics/tracking-plans/preview and /apply, with a body containing project_id and mutation (plan_id, mutation_key, expected_revision, name, definition, delete). Use decimal strings for revisions. API keys and external OAuth cannot use these session routes. Unknown/duplicate JSON fields and query parameters are rejected. Successful apply responses are audited from the owning revision receipt; preview requests make no mutation audit record.

Portable documents

Use a portable file to review declarations in source control or copy a definition into a target project. It contains only this envelope and a complete version 1 definition:

{"format":"anectico.tracking-plan","version":1,"name":"Checkout","definition":{"version":1,"events":[{"name":"checkout_completed","properties":[{"key":"amount","types":["number"],"required":true}]}]}}

Files are limited to 1 MiB; unknown fields and duplicate object fields are refused. Names are at most 200 UTF-8 bytes. Definitions allow up to 100 events, 250 properties per event and literal JSON types (string, number, boolean, object, array, null). No ownership, plan ID, revision, mutation key, capture data or audit history belongs in the document.

# Export one explicitly pinned retained revision, with portable JSON on stdout.
anectico --project "$PROJECT_ID" analytics tracking-plans export "$PLAN_ID" --revision 2 > plan.json
# Read-only validation in the target project; outputs the authored document on success.
anectico --project "$TARGET_PROJECT_ID" analytics tracking-plans validate --file plan.json
# Preview creating a new target ID; repeat with the returned token to commit.
anectico --project "$TARGET_PROJECT_ID" analytics tracking-plans import "$NEW_PLAN_ID" --file plan.json --idempotency-key "$MUTATION_ID"
anectico --project "$TARGET_PROJECT_ID" analytics tracking-plans import "$NEW_PLAN_ID" --file plan.json --idempotency-key "$MUTATION_ID" --confirm-token "$CONFIRM_TOKEN"

Export requires a positive revision; it never silently exports a moving head. Retired sources cannot be exported. Standalone validation needs only read permissions. It checks the definition and current target-project authority, writes nothing and does not query captures, reserve an ID, check whether a future mutation will conflict or grant future write approval.

For replacement, review the full server-normalized current and proposed definitions:

anectico --project "$PROJECT_ID" analytics tracking-plans diff "$PLAN_ID" --expected-revision 2 --file plan.json --idempotency-key "$MUTATION_ID"
anectico --project "$PROJECT_ID" analytics tracking-plans apply "$PLAN_ID" --expected-revision 2 --file plan.json --idempotency-key "$MUTATION_ID" --confirm-token "$CONFIRM_TOKEN"

diff never writes and does not accept a confirmation token. Its output is a current/proposed configuration review, not a line-by-line edit script. apply previews unless given the matching token. Both require an explicit target revision: 0 creates, a positive value replaces that exact head. Import always creates a new ID. Keep the same mutation UUID and arguments after an ambiguous failure. Confirmation binds document values and mutation arguments, including the exact target revision; it does not bind whitespace or JSON object-key order. Editing values requires a new preview. Validation preserves authored values; storage, export and previews normalize declaration order and may omit default false fields.

MCP uses validate_tracking_plan with project_id and document through execute_read_action. For portable export, call get_tracking_plan with format: "portable" and an explicit positive revision string. The usual configuration_json untrusted wrapper now contains the portable envelope; trusted source coordinates remain alongside it. Create/update actions accept document as an alternative to name plus definition, never both. Delete does not accept documents. These actions retain the confirmation and scope requirements described above.

A portable file carries declared sensitivity for that revision, not the source project's persistent effective protection history. Import does not transfer that history and cannot clear existing target protections. Keep project content policy and sensitivity review separate; a successful import is not evidence of instrumentation deployment, capture or completeness.

Observe drift for a pinned event

In Configure → Tracking plans, open Compare observed properties. Select a plan, enter its exact positive revision and Load declaration, then choose a declared event. Set the explicit UTC start and exclusive end times, plus an optional exact environment, and choose Compare observed properties. A journey result's Inspect property-level drift link carries its plan, revision, event, environment and exact event-time window into this form; load the declaration before running. This is a fresh observation, so its first-receipt cutoff and counts may differ from the earlier journey summary.

The table shows declared types and required fields alongside disclosed present/missing counts, observed type counts and unexpected types. Evidence unavailable retains null counts; it never becomes an invented missing-field violation. Not declared is informational. Long comparisons have Previous properties and Next properties controls, 25 rows per page. Changing inputs or retrying clears old evidence; project/session/permission changes reset the workflow. The expanded bounds identify the exact plan/hash/window and observation read times.

REST clients use GET /api/v1/analytics/tracking-plans/{plan_id}/drift with project_id, positive revision, exact event, start_time, end_time and optional environment. This non-cacheable read returns the structured report directly. Unknown/repeated query parameters are refused. REST/dashboard require tracking_plans:read and analytics:read; MCP additionally requires mcp:read. The shared backend rechecks the retained declaration and current authority before returning, including current content permission when originally granted.

Compare one exact declared event with permitted stored observations in the target project:

anectico --project "$PROJECT_ID" analytics tracking-plans drift "$PLAN_ID" --revision 2 --event checkout_completed --start 2026-09-13T00:00:00Z --end 2026-09-14T00:00:00Z --environment production

Requires tracking_plans:read, analytics:read and mcp:read. Additional content access and current policy govern which stored property keys and counts may be disclosed. Use MCP get_tracking_plan_drift through execute_read_action with project_id, plan_id, a positive revision string, event, start_time, end_time and optional environment. Empty environment selects all environments. The half-open event-time window accepts millisecond precision and spans at most 90 days, subject to current readable retention and query limits.

The result includes exact plan/revision/hash coordinates and report_json, containing escaped JSON inside the usual single untrusted wrapper. Decode it as data, never instructions. The report includes the full permitted schema observation, selection, first-receipt cutoff, observation times, unknown platform/upstream completeness, and a comparison per declared or disclosed property. Counts are bounded by the schema reader's 250,000-event limit; exceeding a query budget refuses the report rather than truncating it into an apparently complete result.

  • warning: a disclosed property has an observed undeclared JSON type, or a required property is missing from some stored events in the selected sample. Required means present: null is a value, and is permitted only if null is among the declared types.
  • observed_match on a property, or no_observed_mismatch overall: no mismatch was observed in the disclosed sample. Neither means healthy deployment, complete delivery or plan-wide conformance.
  • unknown: zero selected events or no disclosed schema row for a declared property. The key may have been withheld or never observed. Missing/present counts remain null; the report does not convert unavailable evidence into a missing-field violation.
  • undeclared: a disclosed stored property has no declaration in this event. This is informational; tracking plans are not closed capture allowlists.

A warning takes precedence in the overall status, but unknown property rows remain visible. The report compares the selected retained revision even if a newer head exists. Retired plans are unavailable. ingested_before is a server first-receipt cutoff, not a processed watermark or frozen snapshot; repeated reads can change as data arrives. Each event check is independent. This action never changes a plan or rejects a capture, and it cannot prove a complete critical journey.