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.
Related Docs
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.
