# Access levels and mappings

An access level is a stable capability your application checks, such as `pro`. Store SKU names can differ across platforms while both map to the same access level. This keeps application feature checks independent of product naming and pricing changes.

## Create an access level

Open **Access levels → New access level**, choose an immutable lookup key, and enter a display name. The lookup key is the value used by application code. A display-name edit is not a rename of that key.

Then add a mapping from the appropriate app/platform/product to the access level. A mapping is explicit: a purchase of an unmapped SKU does not automatically grant a similarly named level.

## Choose the grant mode

| Mode | Behavior | Typical use |
| --- | --- | --- |
| `store_managed` | Access follows verified store state and expiry | A recurring subscription |
| `lifetime` | Access has no scheduled expiry, but verified revocation/refund still matters | A permanent unlock |

Lifetime is a catalog rule, not a manual grant to a chosen customer. The current dashboard does not provide an arbitrary customer-access override flow.

## Read access from your app

The subscriber API returns an access-level map keyed by the lookup key:

```json
{
  "app_user_id": "user-123",
  "customer_id": "22222222-2222-4222-8222-222222222222",
  "access_levels": {
    "pro": {
      "active": true,
      "expires_at": "2026-10-09T10:00:00Z",
      "platform": "apple",
      "product_id": "pro.monthly",
      "last_verified_at": "2026-09-09T10:00:00Z",
      "event_id": "123"
    }
  }
}
```

The SDK exposes the corresponding language-native `accessLevels` map. Check its `active` value. A missing key does not grant access. `expires_at` can be absent for an access grant without scheduled expiry. This is an illustrative response, not a live entitlement certificate.

## How multiple purchases combine

CherrySub maintains per-store-identity, per-product grants and resolves their union. One expired Apple subscription cannot mask a different active Google purchase or a lifetime unlock. An observation replaces only the affected identity's grant contribution.

Cancellation of auto-renewal is not immediate expiration. Current access is evaluated against persisted expiry at read time, so it becomes inactive at the boundary even without a timer event. Do not assume a webhook will arrive at that exact wall-clock instant.

## Mapping changes are versioned

Creating, changing, or ending a mapping establishes a safe event boundary. Old source events retain the mapping version that applied when they were observed. New authoritative observations use the applicable newer version.

This means changing a mapping does not silently reprocess every past transaction into a different access history. Existing purchases may reflect the rule change on their next new authoritative observation. Validate migration behavior with a representative sandbox customer before broad catalog changes.

## Inspect and troubleshoot

Access-level details show mapped products and customer grant rows. A mapping can exist while no customer currently has active access. Inspect the customer's current access, transaction expiry/revocation, product ID, app scope, and ownership.

Use [Customers](/docs/customers) to trace a specific case. Use the [runtime API reference](/docs/api-reference) for access-level creation, display-name updates, archival, and mapping changes available to trusted server integrations. Archival preserves historical/replay records; it is not a deletion of evidence.
