Licensing
Licensing is what connects a purchase to your code. As merchant of record, UseThatApp owns the commercial relationship — checkout, tax, renewals, refunds, cancellations — and your app reads the result through one call. It is always authoritative and live: a buyer is entitled the moment hosted checkout completes (the license is created synchronously — no webhooks, no polling), and a refunded license stops being entitled immediately.
Two ways to make that call
The only difference is whose login your customer uses. Both read the same licenses and report the same statuses, so you can switch later without breaking anything.
Keep your own auth
Auth0, Clerk, homegrown — your login stays in charge. Each purchase mints a license key your server validates.
Included in the base per-sale fee, nothing to enable → License Keys
Hosted sign-in (this page)
We run sign-in, passwords, and recovery. The entitlement answers from that same session — no keys to store, and free tiers are covered too.
A paid add-on you switch on per app, free for an introductory period.
The entitlement endpoint requires the add-on
If the Hosted sign-in add-on is off for an app, its entitlement calls answer 403 service_not_enabled — a valid, correctly-scoped token, but the service is not turned on. Nothing the end user does can fix it; the app's owner enables it under Manage app → Integration → Hosted sign-in add-on. Sign-in itself keeps working either way. If you would rather not take the add-on, use license keys instead — they are included in the base fee.
One call, wherever you gate
After a user signs in (see Sign in with UseThatApp), call get_entitlement(access_token) / getEntitlement(accessToken) anywhere you gate paid functionality. No webhooks to receive, no license files to verify.
The call answers one question — is this purchase active, and for which product. Mapping the pseudonymous sub onto your own user or team record, enforcing seats or usage limits, and reacting when a plan changes remain your code; the framework guides show the patterns.
Gate on product_id
Branch on the stable product_id (an opaque prod_… identifier that survives plan renames), never the mutable version display name. Product ids are on your dashboard (the copy button on each version), in the MCP list_products tool, and on every price in the Pricing API — so the plan a buyer purchases is exactly the plan you unlock.
Read product_public_id
Entitlement responses carry the prod_… value in product_public_id, and product_id currently still carries an older database UUID that will be replaced by the same prod_… value. Compare against product_public_id and your gating keeps working through that change; the ids you copy from the dashboard, MCP, and the Pricing API are all the prod_… form.
Python
from usethatapp import get_entitlement, purchase_url
ent = get_entitlement(access_token)
if ent.entitled and ent.product_public_id == "prod_<your-pro-id>":
unlock_pro_features()
else:
# Close the loop: send them to hosted checkout. They return
# signed in with the entitlement already live.
offer_upgrade(purchase_url("<prc-price-id>"))JavaScript
import { getEntitlement, purchaseUrl } from "usethatapp";
const ent = await getEntitlement(accessToken);
if (ent.entitled && ent.product_public_id === "prod_<your-pro-id>") {
unlockProFeatures();
} else {
// Close the loop: hosted checkout — they return signed in,
// entitlement already live.
offerUpgrade(purchaseUrl("<prc-price-id>"));
}Entitlement fields and statuses
| Field | Meaning |
|---|---|
entitled | True if the user may use the app (an active license, or a free tier). |
product_public_id | Opaque product identifier (prod_…) — gate on this. Matches the product_id on every price in the Pricing API. |
product_id | Equal-valued alias of product_public_id — both carry the same prod_… value. |
version | Plan display name (e.g. "Pro") — for showing the user only. |
status | See the status table below. |
is_free / period_end | Whether it's the free tier; the ISO date the current period ends (or null). |
| Status | Meaning |
|---|---|
active | A paid subscription in good standing. |
trialing | In a trial period. |
one_time_active | A one-time (non-recurring) purchase. |
free | The app's free tier. |
preview | The developer's own preview entitlement — set by the dashboard preview and by the purchase-flow walkthrough. |
none | No entitlement. |
Errors
- 401 →
UtaTokenError— the access token is missing, expired, or revoked (a confirmed logout lands here). Refresh, or send the user back through login. - 403 →
UtaPermissionError— a valid token that lacks theentitlementsscope. - 403
service_not_enabled— a valid, correctly-scoped token, but the app has not enabled the Hosted sign-in add-on (see above). Not a user-fixable error: surface it to yourself, not to the buyer.
Full hierarchy and handling patterns: Error Handling.
Going further
- Not on Python or JavaScript? The entitlement endpoint is plain HTTP with a Bearer token — request, response, and error tables are in the REST API reference.
- Selling from your own website? Buyers coming through a purchase link return to your app with a live entitlement, created synchronously — start with Sell from your own website.
- Who is the user? Identity (the pairwise
sub) comes from the login, not the entitlement — see Authentication. - Already have your own accounts? License Keys verify purchases without sending anyone through our login.
