---
title: "Restore Store Purchases"
method: POST
path: "/api/billing/store/restore"
tags: ["billing"]
---

# Restore Store Purchases

`POST /api/billing/store/restore`

## Request

- Base URL: `https://api.retirementlab.app` (the document declares no server; this is the origin it was published from)
- URL: `https://api.retirementlab.app/api/billing/store/restore`
- Auth: HTTP bearer

Re-verify the caller's prior store purchases and re-link them to the account.

Reinstall / new-device flow: the client gathers its current store entitlements
(StoreKit ``currentEntitlements`` / Play ``queryPurchases``) and sends them
here; the server re-verifies each against the store and provisions the active
ones. Idempotent — restoring an already-linked purchase does not duplicate it.

## Request body

- StoreRestoreRequest
  - `platform` 'ios' | 'android', required
  - `transactions` StoreRestoreItem[]
    - `product_id` string
    - `transaction_id` string
    - `signed_transaction` string
    - `purchase_token` string

## Response `200`

Successful Response

- StoreRestoreResponse
  - `restored` integer, required
  - `settled` StoreSettledTransaction[]
    - `transaction_id` string
    - `purchase_token` string
  - `session` AuthSessionResponse, required
    - `user` AuthUser, required
      - `id` integer, required
      - `provider` string, required
      - `name` string, required
      - `email` string
      - `picture` string
    - `anonymous` boolean
    - `account_tier` string
    - `scenario_cap` integer
    - `run_cap` integer
    - `runs_remaining` integer
    - `run_limit` RunLimit: Structured run-allowance summary returned for every user. ``scope`` selects which allowance applies: - ``anonymous_lifetime``: anonymous lifetime sign-in run cap. - ``member_daily``: signed-in free member's daily compute allowance. - ``unlimited``: Pro/pass users have no compute cap (limit/used/remaining and reset_at are all null). ``reset_at`` is the exact instant the daily window rolls over (member_daily only) so the frontend can render it in the user's local timezone.
      - `scope` 'anonymous_lifetime' | 'member_daily' | 'unlimited', required
      - `limit` integer
      - `used` integer
      - `remaining` integer
      - `reset_at` string, date-time
    - `billing_enabled` boolean
    - `has_stripe_customer` boolean
    - `paid_access_expires_at` string, date-time
    - `billing_plan` string
    - `has_active_subscription` boolean
    - `can_start_stripe_checkout` boolean
    - `can_start_store_purchase` boolean
    - `trial_eligible` boolean
    - `pro_plan_price_usd` integer
    - `free_n_scenarios` integer
    - `signup_conversion` boolean
    - `missing_terms_acceptance` boolean
    - `auth_token` string
    - `auth_token_expires_at` string, date-time

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/retirementlab/apis/income-simulator-api.md) · [All operations](https://skmtc.dev/retirementlab/apis/income-simulator-api/llms.txt) · [OpenAPI document](https://skmtc.dev/retirementlab/apis/income-simulator-api/revisions/b0f00a0333fd?raw)
