Skip to main content
A snap token is an encoded timestamp returned by every write operation in Permify. Passing it back on subsequent read and check requests tells Permify to evaluate against a snapshot that is at least as fresh as the moment the data was written.

The problem snap tokens solve

Permify is built for distributed deployments where multiple replicas serve requests. Without a consistency mechanism, a check request might land on a replica that hasn’t yet received the latest write — returning a stale result even though the data already exists in the primary database. Snap tokens solve this with snapshot reads: every check is anchored to a specific point in time, preventing false positives and false negatives caused by replication lag.
Permify aggressively caches permission check results to deliver low latency at scale. The cache key includes the snap token, so a cached result is only reused when the snapshot matches. If a relationship or attribute changes, the snap token changes, and the previous cache entry is no longer served — keeping the cache consistent without manual invalidation.

How snap tokens work

1

Write data

Call the Write Data API to create or delete relationships and attributes. Every successful write returns a snap_token in the response body.
{
  "snap_token": "gp/twGSvLBc="
}
2

Store the snap token

Associate the snap token with the resource it describes. A common approach is adding a snap_token column to the relevant table in your application database and updating it on every write.
3

Pass the snap token on reads and checks

Include the stored snap token in the snap_token field of subsequent Check, LookupEntity, LookupSubject, or Read requests. Permify ensures the result reflects a database state that is at least as fresh as that token.
{
  "metadata": {
    "schema_version": "ce8siqtmmud16etrelag",
    "snap_token": "gp/twGSvLBc=",
    "depth": 20
  },
  "entity": {
    "type": "repository",
    "id": "1"
  },
  "permission": "edit",
  "subject": {
    "type": "user",
    "id": "1"
  }
}

When snap token is not provided

If you omit snap_token, Permify retrieves the ID of the latest transaction from the transactions table and uses it as the snapshot anchor. This ID represents the most current state of the database. When two identical requests arrive without a snap token, both query for the latest transaction ID. The first request to complete writes its result to the cache under a key such as:
check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request}
The second request generates the same key and receives the cached result directly, without re-running the check engine. This means omitting snap tokens still produces correct results — but it requires an extra database round-trip per unique request to fetch the latest transaction ID.
Use snap tokens when you need the strongest consistency guarantee and want to avoid the extra database lookup on every unauthenticated request. Store the snap token returned by Write alongside the resource record and pass it on every downstream check.

Snap tokens and the cache

The snap token is embedded directly in the permission cache key. There is no separate snap-token cache — the same permission.cache configuration governs both:
Config keyEffect
service.permission.cache.max_costMaximum memory budget for cached check results (e.g. 10MiB). Controls how many snap-token-keyed entries can live in memory at once.
service.permission.cache.number_of_countersNumber of TinyLFU admission counters. A good rule of thumb is ~10× the expected number of unique cached items.
Eviction is driven by memory pressure using Ristretto’s TinyLFU/SampledLFU policy — not by a fixed TTL. When a schema version changes, the schema_version component of the key changes and all prior entries become stale naturally. See Caching for a full description of Permify’s cache mechanisms.

Build docs developers (and LLMs) love