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.
create_discount_code Create a discount code redeemable at checkout. Choose flat (cents off) or percent (basis points off), set an optional expiresAt, an optional redemption cap, and an optional app scope so the code only works for specific listings.
list_discount_codes List the discount codes you've created, optionally filtered by storeAppId or discount type. Returns the codes you can act on.
create_affiliate Create an affiliate with a unique referral code, a payoutShareBp (basis points of net sale paid out), and an optional app scope so the affiliate only earns on specific listings.
update_affiliate Update an existing affiliate's state (active/inactive), payoutShareBp, display name, contact email, or app scope. Idempotent.
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). Enforces the 7-day refund window and 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.

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 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 7-day window, 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 → In-App Purchases

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

Store → Refunds

Full and partial refund flows, the 7-day window, entitlement side effects, and the refund_purchase MCP tool.

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.