---
title: "S15 bindings"
description: "Data-binding map of Paywall · Paywall: every field to its source, and what each required state shows."
source: "design/hifi/S15/bindings.md"
---

> Part of [Brand and design](/design/llms.txt). Every page: [llms.txt](/llms.txt).

# Data-binding map: S15 Paywall · Paywall

> Generated by `botgumi-design index` from `../../screens.json`. Edit that file, not this page.

Every field [S15](/design/hifi/s15/index.md) shows and where it comes from.

| Field | Source (endpoint or local state) | Format | Fallback |
|---|---|---|---|
| score | `api GET /paywall diagnostic.overall` · `paywall.diagnostic.overall` | display | — |
| band label | `api GET /paywall diagnostic.band_label` · **CCR-12** / derived from D3 | `band.*` | — |
| weakest | `api GET /paywall diagnostic.weakest_label` · `paywall.diagnostic.weakest_label` (Accept-Language) | `paywall.weakest` | hide line |
| title skill | `api GET /paywall diagnostic.one_action_title` · `paywall.diagnostic.one_action_title` (Accept-Language) | `paywall.title` | generic title without skill |
| K slider | `local paywall.selectedWeeklyDays` · `local:paywall.selectedWeeklyDays`, default `onboarding.weekly_days` (**CCR-7**) | 3 / 5 / 7 detents | 5 |
| K slider | `local onboarding.weekly_days` |  |  |
| projection | `derived projection` · `derived:` `paywall.benchmarks[]` where `weekly_days = K` and `baseline_bucket = bucket(diagnostic.overall)` → `day14_avg_overall`, `sample_n`, `weakest_dim_avg_delta` | `paywall.projection` (+ `weakest_delta`) — see [paywall.md](/design/paywall/index.md) | `sample_n < 30` or missing → `paywall.projection.insufficient` (no number) |
| pilot label | `api GET /paywall benchmark_source` · `paywall.benchmark_source = pilot_benchmark` → `paywall.benchmark_label` (Accept-Language) | amber tag | `paywall.source.pilot` key |
| pilot label | `api GET /paywall benchmark_label` |  |  |
| calendar days | `api GET /paywall plan_preview.days[]` · `paywall.plan_preview.days[]` + `derived:` practice pattern for K + streak milestones reachable by that pattern + billing day = trial_days | 7-col grid, Monday first | if plan missing: hide calendar, keep timeline |
| calendar days | `derived practice_pattern` |  |  |
| timeline dates | `derived trial_timeline` · `derived:` today + `trial_days` − 1 (reminder), today + `trial_days` (billing), plan day 14 | `Date.FormatStyle` | — |
| billing price | `store displayPrice` · `storekit[id].displayPrice` + `subscriptionPeriod` | `paywall.timeline.billing.*` | `paywall.timeline.billing.loading` |
| billing price | `store subscriptionPeriod` |  |  |
| product cards | `api GET /paywall products[]` · `paywall.products[]` (kind, trial_days, is_default, badge — badge Accept-Language) × `storekit[id]` (displayPrice, price, introductoryOffer, isEligibleForIntroOffer) | per-month = `price / 12` with `priceFormatStyle` | StoreKit not loaded → skeleton cards, CTA disabled |
| product cards | `store product` |  |  |
| CTA + caption | `derived cta_caption` · `derived:` selected product, trial eligibility, billing date | `paywall.cta.trial` / `paywall.cta.subscribe` + caption | loading / purchasing / failed / restored states own the CTA |
| expired · stats | `api GET /me stats.completed_sessions` · `me.stats.completed_sessions`, `me.stats.ability`, `me.focus.title` | — | ability null → "—" |
| expired · stats | `api GET /me stats.ability` |  |  |
| expired · stats | `api GET /me focus.title` |  |  |
| expired · trial end date | `api GET /me entitlement.expires_at` · `me.entitlement.expires_at` / `trial_ends_at` | date | omit date |
| expired · trial end date | `api GET /me entitlement.trial_ends_at` |  |  |
| legal | `api GET /paywall legal.terms_url` · `paywall.legal.terms_url / privacy_url` | link | bundled URLs |
| legal | `api GET /paywall legal.privacy_url` |  |  |
| `AppStore.sync()` | `store restore` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `AppStore.sync() → POST /v1/subscriptions/apple/transactions` | `store restore` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `AppStore.sync() → POST /v1/subscriptions/apple/transactions` | `call POST /subscriptions/apple/transactions` |  |  |
| `Product.purchase()` | `store purchase` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `Product.purchase() → POST /v1/subscriptions/apple/transactions` | `store purchase` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `Product.purchase() → POST /v1/subscriptions/apple/transactions` | `call POST /subscriptions/apple/transactions` |  |  |
| `StoreKit Product.PurchaseResult / error` | `store purchase_result` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `derived: band(paywall.diagnostic.overall)` | `api GET /paywall diagnostic.band_label` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `derived: introductoryOffer + price/12 (priceFormatStyle)` | `derived monthly_price` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `derived: trial end = today + trial_days; storekit.displayPrice` | `derived trial_timeline` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `derived: trial end = today + trial_days; storekit.displayPrice` | `store displayPrice` |  |  |
| `local:paywall.selectedWeeklyDays (default onboarding.weekly_days)` | `local paywall.selectedWeeklyDays` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `paywall.benchmark_source = pilot_benchmark → benchmark_label` | `api GET /paywall benchmark_source` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `paywall.benchmark_source = pilot_benchmark → benchmark_label` | `api GET /paywall benchmark_label` |  |  |
| `paywall.diagnostic.weakest_label (Accept-Language)` | `api GET /paywall diagnostic.weakest_label` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `paywall.legal.privacy_url` | `api GET /paywall legal.privacy_url` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `paywall.plan_preview.starts_on – ends_on` | `api GET /paywall plan_preview.starts_on` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `paywall.plan_preview.starts_on – ends_on` | `api GET /paywall plan_preview.ends_on` |  |  |
| `paywall.products[kind=annual].badge (Accept-Language)` | `api GET /paywall products[].badge` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `storekit.subscriptionPeriod` | `store subscriptionPeriod` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `storekit[clarisay.pro.annual]` | `store product` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `storekit[clarisay.pro.annual].displayPrice` | `store displayPrice` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `storekit[clarisay.pro.monthly]` | `store product` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |
| `storekit[clarisay.pro.monthly].displayPrice` | `store displayPrice` · From the board's `data-bind`; no row in data-binding.md yet. |  |  |

## Derived values

Computed on device; each from the sources it names.

| Value | From | How |
|---|---|---|
| `projection` | `api GET /paywall benchmarks[].weekly_days`, `api GET /paywall benchmarks[].baseline_bucket`, `api GET /paywall benchmarks[].day14_avg_overall`, `api GET /paywall benchmarks[].sample_n`, `api GET /paywall benchmarks[].weakest_dim_avg_delta`, `api GET /paywall baseline.overall`, `local paywall.selectedWeeklyDays` | The benchmark row for K and the baseline's bucket: day-14 average, sample size and the weakest dimension's gain; hidden below the minimum sample ([paywall.md](/design/paywall/index.md)). |
| `practice_pattern` | `local paywall.selectedWeeklyDays`, `api GET /paywall plan_preview.days[]` | Which calendar days a K-days-a-week pattern trains, and the streak milestones it reaches. |
| `trial_timeline` | `device clock`, `api GET /paywall products[].trial_days`, `api GET /paywall plan_preview.ends_on` | Today, the reminder the day before billing (today + trial_days − 1), billing day and plan day 14. |
| `cta_caption` | `local paywall.selectedProduct`, `store isEligibleForIntroOffer`, `store introductoryOffer`, `derived trial_timeline` | The button and its caption: trial or not, and the first billing date. |
| `monthly_price` | `store price`, `store introductoryOffer` | The annual price over 12, in the store's currency format. |

## Binds on the boards

The `data-bind` values across every state.

- `AppStore.sync()`
- `AppStore.sync() → POST /v1/subscriptions/apple/transactions`
- `Product.purchase()`
- `Product.purchase() → POST /v1/subscriptions/apple/transactions`
- `StoreKit Product.PurchaseResult / error`
- `derived: band(paywall.diagnostic.overall)`
- `derived: introductoryOffer + price/12 (priceFormatStyle)`
- `derived: trial end = today + trial_days; storekit.displayPrice`
- `local:paywall.selectedWeeklyDays`
- `local:paywall.selectedWeeklyDays (default onboarding.weekly_days)`
- `me.focus.title`
- `me.stats.ability`
- `me.stats.completed_sessions`
- `paywall.benchmark_source = pilot_benchmark → benchmark_label`
- `paywall.diagnostic.one_action_title`
- `paywall.diagnostic.overall`
- `paywall.diagnostic.weakest_label (Accept-Language)`
- `paywall.legal.privacy_url`
- `paywall.legal.terms_url`
- `paywall.plan_preview.starts_on – ends_on`
- `paywall.products[kind=annual].badge (Accept-Language)`
- `storekit.subscriptionPeriod`
- `storekit[clarisay.pro.annual]`
- `storekit[clarisay.pro.annual].displayPrice`
- `storekit[clarisay.pro.monthly]`
- `storekit[clarisay.pro.monthly].displayPrice`

## States

- Loading: From [states.md](/design/states/index.md) (Paywall): [paywall--loading-products](/design/gallery/paywall/paywall--loading-products.html) (StoreKit) + skeleton projection (`/paywall`)
- Empty: From [states.md](/design/states/index.md) (Paywall): —
- Error (retryable / not): From [states.md](/design/states/index.md) (Paywall): `/paywall` failed → projection & calendar replaced by Retry card; products still purchasable · fatal: StoreKit unavailable → "App Store unavailable" + Restore
- Offline: From [states.md](/design/states/index.md) (Paywall): cached `/paywall`; purchase disabled with "Connect to subscribe"
