---
title: "Verify Store Purchase"
method: POST
path: "/api/billing/store/verify"
tags: ["billing"]
---

# Verify Store Purchase

`POST /api/billing/store/verify`

## 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/verify`
- Auth: HTTP bearer

Verify a native purchase server-side and provision store-channel entitlement.

Never trusts the client-reported purchase: the transaction is re-verified
against Apple/Google before any entitlement is recorded. Blocks the purchase
when an active Stripe subscription already provides Pro access (double-billing
guard) and when the transaction belongs to another account.

## Request body

- StoreVerifyRequest: Client-reported purchase to verify server-side. The client values are NOT trusted for the grant — they are only the lookup keys we re-verify against the store's server API.
  - `platform` 'ios' | 'android', required
  - `product_id` string, required
  - `transaction_id` string
  - `signed_transaction` string
  - `purchase_token` string

## Response `200`

Successful Response

- AuthSessionResponse
  - `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)
