# Discover event property schemas

> Inspect permitted property names, JSON types and quality counts through MCP or the CLI.

Canonical page: https://anectico.com/docs/investigate/event-schema/


Use `get_event_schema` through the MCP `execute_read_action` tool, or run:

```bash
anectico --project PROJECT_UUID analytics schema --event checkout_completed \
  --start-time 2026-09-10T00:00:00Z --end-time 2026-09-11T00:00:00Z \
  --environment production
```

The equivalent REST endpoint is `GET /api/v1/events/schema`. MCP and REST arguments
are `project_id`, `event`, `start_time`, `end_time` and optional `environment`.
Choose an exact event name, or discover names with `get_event_catalog` first.
The start is inclusive and the end exclusive. Use RFC3339 timestamps at millisecond
precision or coarser, spanning at most 90 days. A future end does not establish
completeness for that future period. Environment matches exactly; omitting it
includes all environments. Environment strings are exact, up to 32,768 UTF-8 bytes;
whitespace is not trimmed. Event names are limited to 200 UTF-8 bytes.

## Permissions and visibility

The read requires `analytics:read`; MCP also needs `mcp:read`. A project credential
supplies its project and cannot select another. Organization credentials must
choose a project. The project must be active and the full window readable under
its retention rules.

The response's `visibility` is `permitted_stored_properties`. Recognized benign
metadata is available without content permission unless it carries a declared-sensitive
protection. Protected keys require content permission plus both attribute-content READ
classes and their full-window retention, even when the key normally counts as benign.
These protections survive deletion of the declaring plan. Other property summaries also
require `agents:content:read` and the project's current content READ policy.
Unknown property names require both applicable attribute-content classes. A
class is omitted if its retention does not cover the full requested window;
narrowing the window may reveal still-retained properties. Content scope cannot
override a project policy denial.

No raw examples or property values are returned. Nested content is checked too:
if any occurrence contains a refused nested value, that property's entire
summary is withheld. Stored redaction can also remove fields. An omitted name
does not prove the application never sent it. The response does not enumerate
withheld names or their counts.

## Read the result

`observed_events` counts converged stored occurrences for the selected event,
including retries collapsed into one occurrence. It does not count people.
Each property contains:

- `key`: the literal top-level name; dots are not interpreted as a nested path.
- `present` and `missing`: presence or absence in the observed stored bags.
- `types`: nonzero counts for `string`, `number`, `boolean`, `array`, `object` and `null`.
- `finite_numbers`: the subset of number values eligible for finite binary64 numeric measures.
- `first_seen` and `last_seen`: event times of present observations within this window.

Numeric-looking strings stay strings. A JSON number outside finite numeric range
stays a number but is excluded from `finite_numbers`. Explicit null is present,
not missing. The type counts sum to `present`; `present + missing` equals
`observed_events`. Missing describes stored data, not proof the SDK omitted a field.

Both `platform_completeness` and `upstream_completeness` remain `unknown`, even
for zero events or an empty schema. `ingested_before` is a first-receipt cutoff;
`observed_from` and `observed_until` describe the database read. These are not a
consumption watermark, frozen snapshot or completeness proof.

The operation returns a whole bounded schema without pagination. It currently
allows at most 250 distinct property keys of up to 512 UTF-8 bytes each and 250,000 observed events, with an
8 MiB source-string budget and a ten-second service deadline. Database resource
limits also apply. Exceeded limits fail the whole request. Narrowing the event window may reduce the
selected events, bytes or property diversity; it cannot reduce one event's property bag.
Unreadable storage or sensitivity declarations, changed permissions/policy/protections
or elapsed retention cannot become
a successful partial result. Start a fresh read after a policy-change error. A temporarily unavailable source
returns HTTP 503 with `Retry-After: 1`; retry after that delay.

This discovery workflow describes observed instrumentation. It does not publish
tracking plans, enforce a declared application schema or add raw property examples.

## Reserved properties

`$release` and `$app_version` are reserved, well-known string properties every SDK may stamp on
a product event: `$release` is the app or service's full release identifier (for example
`checkout-service@2.4.1` or `web@2.4.1+318`); `$app_version` is the bare version string alone
(for example `2.4.1`). Both are **event** properties, never person properties — they describe
what a person did (which build produced this occurrence), not who the person is, so they are
never offered as current-person filters, only as `source: "event"` filters and breakdowns.

Neither is invented by an SDK that has nothing configured: an event captured before a release is
set, or by an SDK that never sets one, simply carries neither property, the same as any other
absent property. See each SDK's own reference page for exactly what it stamps and how to
configure it: [iOS](/docs/reference/ios-sdk), [Android](/docs/reference/android-sdk),
[JavaScript](/docs/reference/javascript-sdk), [Go](/docs/reference/go-sdk),
[Python](/docs/reference/python-sdk), [React Native](/docs/reference/react-native-sdk),
[Flutter](/docs/reference/flutter-sdk).

Filter or break down on `$release`/`$app_version` exactly like any other event property (see
[Product analytics](/docs/investigate/product-analytics)) — there is no separate release
primitive. Comparing a metric across two releases with non-overlapping active periods is a
comparison-window query whose event filter additionally pins `$release`; comparing several
releases active at once is a breakdown on `$release`.
