Store Integration

The Store MCP gives you the full management surface for your listing — register your app, set pricing, publish, and run promos. This page lists each tool, then shows how your app verifies a student has paid for access at launch.

Tools

All tools require Store-admin access for the targeted app.

Tool What it does
register_app Register an already-approved Platform app in the Store catalog. You provide a Store-side slug, a reference to the Platform app (ref), optional plan (priceCents, currency, additionalSeatCents, maxSeats, paymentModel — one of one_time / subscription_monthly / subscription_annual, defaults to one_time), listing metadata (including an optional listing.buyerLtiLaunchEnabled boolean that toggles the Post-Purchase Parent LTI Launch), an optional defaultOrgSourcedId — when set, the platform-side org is verified up front and every entitled student gets a student/primary role in that org on purchase (in addition to the standard user_profile writeback), and an optional webhookUrl — required only if your app will call the in-app purchases endpoint. Returns Cognito M2M clientId/clientSecret for the REST surface and, when webhookUrl is set, a one-shot outboundWebhookSigningSecret for verifying signed webhook deliveries — capture both, they aren't retrievable via subsequent MCP calls. Creates the catalog entry in an unpublished state — call set_app_published once the listing is ready.
update_app_plan Update the plan attached to a Store app: base priceCents, currency, optional additionalSeatCents (per-seat pricing beyond the included seats), optional maxSeats, and optional paymentModel (one_time / subscription_monthly / subscription_annual). Existing values are preserved when fields are omitted, so you can change just one dimension at a time.
create_catalog_item Create a catalog item on a store app's plan: a displayName, priceCents, and ISO 4217 currency. In-app purchases reference these items by id. You supply the planId the item attaches to; grants are checked against the app that owns that plan.
manage_app_image Set the logo, set the cover, add a gallery screenshot, or remove a gallery screenshot. Pass action plus slug and either imageBase64 (base64 bytes, ≤5 MB after encoding) or imageUrl (HTTPS, SSRF-guarded, ≤5 MB, image/* content-type) for the three set/add actions; remove_screenshot takes contentHash instead. Idempotent by content hash within the same kind; the same bytes cannot be reused across different kinds on one app.
set_app_published Flip a Store app's published flag. Setting it to true makes the listing visible on store.timeback.com; false unpublishes it without deleting catalog state. Idempotent.
set_app_refund_window Set or clear an optional storefront purchase refund window on a store app, in days. Pass slug and refundWindowDays (integer 1 to 180, or null to restore the 7-day store default). In-app purchase refunds are unaffected. Requires app:manage_store. See Store → Refunds.
create_discount_code Create a discount code redeemable at checkout. Choose flat (cents off) or percent (basis points off), set a required storeAppId scoping the code to one of your apps, and optionally an expiresAt, a redemption cap, and firstTimeCustomerOnly. Terms are fixed at creation — there is no update tool. See Store → Discount Codes.
list_discount_codes List a single app's discount codes, including retired ones and their state. storeAppId is required; discountType is the only optional filter.
deactivate_discount_code Retire a discount code so it stops validating at checkout. Existing purchases keep their attribution. Permanent — the code can't be reactivated and its string can never be reused. See Store → Discount Codes.
create_affiliate Create an affiliate with a unique referral code, a required storeAppId scoping the affiliate to one of your apps, and an optional payoutShareBps (basis points of net sale — attribution metadata, not a settlement). See Store → Affiliate Codes.
list_affiliates List a single app's affiliates, including retired ones and their state. storeAppId is required; state (active or inactive) is the only optional filter.
update_affiliate Update an existing affiliate's display name, contact email, payoutShareBps, or state (active/inactive). The code string and app scope cannot change. Idempotent. See Store → Affiliate Codes.
deactivate_affiliate Retire an affiliate so its code stops validating at checkout. Existing purchases keep their attribution. Reversible — update_affiliate with state: active brings the same code back. See Store → Affiliate Codes.
refund_purchase Refund a storefront or in-app purchase your app owns (full or partial). Pass purchaseType (storefront or in-app), the purchaseId, the storeAppId that owns it, and — for a partial — amountCents (strictly less than the purchase amount). Storefront refunds use the app's storefront window (7 days unless you set one with set_app_refund_window). In-app refunds always use the 7-day window. Also enforces the one-refund-per-purchase rule. See Store → Refunds for entitlement side effects and outbound webhook payloads.

The tools' full input schemas are served by the MCP server itself — your client fetches them on connection, so the field names and types stay in sync with whatever the server is currently running.

In-App Purchases

Once your app is registered and you've created a catalog item, your backend can charge the parent's saved payment method off-session via POST /apps/v1/in-app-purchases and receive the outcome on the webhookUrl you registered. See Store → In-App Purchases for the full walkthrough — M2M token flow, request/response, and outbound webhook verification.

Discount Codes (Runtime REST)

Create, list, and deactivate codes through the MCP tools above. To check whether a parent can still use a code from your own backend, call POST /apps/v1/discount-codes/validate with the same M2M client-credentials token as in-app purchases (scope https://store.timeback.com/apps/v1/purchases.write). Parent checkout still uses POST /store/v1/discount-codes/validate with a parent session — do not send client-credentials there. See Store → Discount Codes for the request body, 200 shapes, and how campaign backends should read reason.

Refunds

Both storefront purchases and in-app purchases can be refunded (full or partial) via POST /apps/v1/{purchases|in-app-purchases}/{id}/refund or the refund_purchase MCP tool. Storefront refunds use the app's storefront window (7 days unless you set one with set_app_refund_window). In-app refunds always use the 7-day window and are unaffected by a storefront window. Storefront full refunds revoke the student's entitlement and cancel the underlying Stripe subscription (matching the parent self-serve full refund); partial and IAP refunds leave the entitlement active. Every refund fires a signed outbound webhook (purchase.refunded or in-app-purchase.refunded) to the webhookUrl you registered. See Store → Refunds for the full flow, the one-refund-per-purchase rule, and the webhook envelopes.

Post-Purchase Parent LTI Launch

When enabled on your listing, the Store LTI-launches the buyer into your app right from the checkout success page — no manual sign-in, no separate handoff. Off by default; you opt in per app.

Enable it

Pass listing.buyerLtiLaunchEnabled: true on register_app (the tool upserts, so you can flip it on an already-registered app by re-invoking with just this field on listing).

What the parent sees

Under the fulfillment section of store.timeback.com/checkout/success, a cancellable countdown ("Opening your app name in N seconds…" + a Cancel button) plus a persistent Take Me to your app name button. Both open a new tab and POST an LTI 1.3 id_token to your registered launchUrl — the same launch mechanics documented in Level 2: LTI Launch.

What your app receives

The Store appends the just-purchased students' OneRoster sourcedIds to the LTI target_link_uri claim as ?purchasedStudents=<sourcedId> (comma-separated for multi-student checkouts). After your LTI endpoint validates the token and redirects the browser to target_link_uri, your landing page can read the query param and know exactly which kids just came out of checkout:

GET https://your-app.example.com/lti/landing?purchasedStudents=abc-...,def-...

For example, an Express handler:

app.get('/lti/landing', (req, res) => {
  const purchasedStudentSourcedIds = String(req.query.purchasedStudents ?? '')
    .split(',')
    .filter(Boolean);
  // greet the newly purchased students, kick off onboarding, unlock content, etc.
});

The query param is present only on launches originating from a completed Store checkout; other LTI launches into your app (e.g., a returning student from TBA) don't carry it. Treat it as an optional hint, not an authorization signal — always rely on the LTI id_token claims to authenticate the caller.

Check Student Access at Login

When a student launches your app, you need to confirm they have a paid, active profile for it before letting them in. This runs against the Platform's OneRoster API (not the Store) and takes two short requests.

Step 1: Mint a Platform access token

Use your clientId + clientSecret from app registration to mint a Bearer token. See Authentication for the full client-credentials flow:

curl -X POST https://platform.timeback.com/auth/1.0/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "<CLIENT_ID>:<CLIENT_SECRET>" \
  -d "grant_type=client_credentials&scope=https://purl.imsglobal.org/spec/or/v1p2/scope/roster.readonly"

Cache the returned access_token — it's valid for an hour.

Step 2: Resolve the student's sourcedId by email

GET /rostering/1.0/users returns one row when filtered by email. Trim the response to just the sourcedId:

curl "https://platform.timeback.com/rostering/1.0/users?filter=email='student@example.com'&fields=sourcedId" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
{
  "users": [{ "sourcedId": "11111111-2222-3333-4444-555555555555" }],
  "offset": 0,
  "limit": 10,
  "total": 1
}

If total is 0, the email isn't on TimeBack — deny access.

Step 3: Assert an active profile for your app

GET /rostering/1.0/users/{sourcedId}/profiles returns the user's profiles; filter by your applicationId and ask only for the fields you'll inspect:

curl "https://platform.timeback.com/rostering/1.0/users/11111111-2222-3333-4444-555555555555/profiles?applicationId=<YOUR_APP_ID>&userId=11111111-2222-3333-4444-555555555555&fields=profileType,status" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
{
  "userProfiles": [{ "profileType": "learning_app_profile", "status": "active" }],
  "offset": 0,
  "limit": 10,
  "total": 1
}

Treat the launch as authorized only when a profile exists and its status is active. An empty userProfiles array, a 404, or any status other than active means the student hasn't paid for your app (or their purchase was refunded) — deny the launch.

The same two requests run unchanged against sandbox.platform.timeback.com with sandbox credentials — useful when you're testing the entitlement gate before publishing.

Store → Introduction

What the Store provides, the publish path, and the prerequisites.

Store → MCP Setup

Connect Cursor / VS Code / Claude Code / ChatGPT to the Store MCP.

Store → Discount Codes

Creating, scoping, and retiring promo codes, and why their terms are immutable.

Store → Affiliate Codes

Creating, scoping, listing, and retiring referral codes. Terms stay editable; retirement is reversible.

Store → In-App Purchases

Server-to-server purchase endpoint, M2M authentication, and outbound-webhook verification.

Store → Refunds

Full and partial refund flows, the per-app storefront window (7-day default) and the fixed 7-day in-app window, entitlement side effects, refund_purchase, and set_app_refund_window.

Authentication

The client-credentials flow used to mint the Bearer token for the entitlement check above.

Level 0: Register Your App

How to get the applicationId, clientId, and clientSecret referenced throughout this page.