Documentation

Progression & portable-self state

AdminUpdated Sep 14, 2026

Progression is Atlas's cross-property "portable self" state — a user's XP/level (and tier/flair) that can be carried across every property that shares the Atlas hub. A property grants signals; Atlas folds them into durable state and (optionally) projects them into the session token.

Grant a signal (idempotent)

curl -X POST "https://<fapi>/v1/users/<user_id>/progression/events" \
  -H "Authorization: Bearer sk_…" -H "content-type: application/json" \
  -d '{"track":"default","amount":50,"reason":"completed onboarding","idempotency_key":"onboard-2026-09-14"}'
# → { "track":"default", "xp": 350, "level": 4, "level_changed": true, "delta": 50 }
  • Idempotent on (source, idempotency_key) — a retry is a safe no-op that returns current state. source is stamped from the authenticated credential, never the body, so one property can't write as another.

  • level is derived from xp by a configurable curve — never trusted from the caller. Negative amounts (corrections/clawbacks) require the elevated * scope.

Read state

curl "https://<fapi>/v1/users/<user_id>/state" -H "Authorization: Bearer sk_…"
# → { "object": "user_state", "attributes": { "progression": { "default": { "xp": 350, "level": 4 } }, "tier": "...", "flair": "..." }, "entitlements": [], "directives": [], "roles": [] }

Project into the session token

Set public_metadata.progression / .tier / .flair on the user and Atlas lifts them to top-level __session claims (present only when set), so a relying party reads the badge from the token with no extra call. See JWT templates for arbitrary custom claims.

Per-source scoping means a shared hub can host many properties without one overwriting another's signals.

Was this page helpful?