UseThatApp Logo

    Quick Start

    Go from zero to selling your app from your own website — with UseThatApp as the merchant of record handling payment, tax, and refunds. One integration covers the whole loop: live prices and Buy buttons on your site, hosted checkout, the buyer returning to your app, and feature gating on what they bought. The usethatapp SDK runs server-side and does the crypto for you.

    One choice before you start

    How should your app verify a purchase? If it already has accounts, keep them and validate a license key — included in the per-sale fee, and you can skip every sign-in step below. This Quick Start walks the other path, the Hosted sign-in add-on, where we run login and the same session answers what they bought. Selling works the same either way.

    The Loop You're Building

    1. Sell. Your website renders live prices from the pricing API and links each Buy button to hosted checkout — a purchase link on usethatapp.com.
    2. Return. Checkout creates the license synchronously and sends the buyer to your registered Login URL, where your /login route signs them in over OIDC — no webhooks, no polling.
    3. Gate. Anywhere it matters, read the live plan with get_entitlement / getEntitlement and branch on the stable product_public_id. (Keys mode calls validate_license_key here instead and branches on the same field.)

    Prerequisites

    • Python 3.9+ or Node.js 18+
    • An app registered on usethatapp.com
    • Your app's Client ID and Client Secret, from the Integration page of your UseThatApp dashboard

    Step 1 — Install the Package

    Python

    terminal
    pip install usethatapp

    JavaScript

    terminal
    npm install usethatapp

    Step 2 — Register Your App

    Open your app's Integration page on the UseThatApp dashboard and register:

    • A Redirect URI — your OIDC callback URL, matched exactly (an allow-list). It must be HTTPS on your domain (localhost is allowed for development), e.g. https://yourapp.com/callback.
    • A Login URL — the page in your app that starts sign-in. This matters twice: marketplace launches land here, and it's the default place buyers return to after checkout — so make sure it signs them straight in.

    Then copy your Client ID and Client Secret. The secret is shown once — keep it on your server, never in the browser.

    Step 3 — Configure Environment Variables

    Both SDKs read configuration from environment variables, and both accept in-code overrides via configure(...) (Python also reads Django settings). Only the client id, secret, and redirect URI are required — the rest default to production:

    environment
    UTA_CLIENT_ID=...            # required
    UTA_CLIENT_SECRET=...        # required (server-side only)
    UTA_REDIRECT_URI=https://yourapp.com/callback   # required, must match the dashboard
    
    # optional — default to production:
    UTA_ISSUER=https://www.usethatapp.com/o
    UTA_API_URL=https://www.usethatapp.com
    UTA_SCOPES="openid entitlements"

    Keep the client secret out of version control. If your host mounts secrets as files (Render Secret Files, Fly.io, Kubernetes, GCP Secret Manager, etc.) set UTA_CLIENT_SECRET_PATH instead. See the Hosting Provider guide for per-provider recommendations. (The purchase helpers are lighter: purchase_url needs no config at all, and manage_url / get_prices only need UTA_CLIENT_ID — so a separate marketing site can use them without the OAuth secrets.)

    Step 4 — Add Login and Callback Routes

    Expose a /login route that starts sign-in and a /callback route (matching UTA_REDIRECT_URI) that completes it. These routes serve both marketplace launches and buyers returning from checkout. The SDK is framework-agnostic — you wire three small bits: read the callback query params, store flow_state in your session, and issue the redirects.

    Python (Flask)

    app.py
    from flask import Flask, redirect, request, session, url_for
    from usethatapp import (
        UtaError,
        begin_login,
        complete_login,
        get_entitlement,
    )
    
    app = Flask(__name__)
    app.secret_key = "change-me"
    
    @app.route("/login")
    def login():
        auth_url, flow_state = begin_login()
        session["uta_flow"] = flow_state
        return redirect(auth_url)
    
    @app.route("/callback")
    def callback():
        # On cancel/deny, OAuth redirects back with ?error=... and no code.
        if request.args.get("error"):
            session.pop("uta_flow", None)
            return redirect(url_for("home"))
        try:
            s = complete_login(
                code=request.args.get("code"),
                state=request.args.get("state"),
                flow_state=session.pop("uta_flow", {}),
            )
        except UtaError as e:
            return f"login failed: {e}", 400
        session["uta_sub"] = s.sub
        session["uta_access_token"] = s.access_token
        return redirect(url_for("home"))
    
    @app.route("/")
    def home():
        token = session.get("uta_access_token")
        if not token:
            return '<a href="/login">Log in with UseThatApp</a>'
        ent = get_entitlement(token)
        return {"sub": session.get("uta_sub"), "version": ent.version}

    JavaScript (Express)

    app.mjs
    import express from "express";
    import session from "express-session";
    import { beginLogin, completeLogin, getEntitlement, UtaError } from "usethatapp";
    
    const app = express();
    app.use(session({ secret: "change-me", resave: false, saveUninitialized: true }));
    
    app.get("/login", async (req, res, next) => {
      try {
        const { authorizationUrl, flowState } = await beginLogin();
        req.session.utaFlow = flowState;
        res.redirect(authorizationUrl);
      } catch (e) { next(e); }
    });
    
    app.get("/callback", async (req, res) => {
      // On cancel/deny, OAuth redirects back with ?error=... and no code.
      if (req.query.error) {
        delete req.session.utaFlow;
        return res.redirect("/");
      }
      try {
        const s = await completeLogin({
          code: req.query.code,
          state: req.query.state,
          flowState: req.session.utaFlow,
        });
        delete req.session.utaFlow;
        req.session.utaSub = s.sub;
        req.session.utaAccessToken = s.access_token;
        res.redirect("/");
      } catch (e) {
        res.status(e instanceof UtaError ? 400 : 500).send(`login failed: ${e.message}`);
      }
    });
    
    app.get("/", async (req, res) => {
      const token = req.session.utaAccessToken;
      if (!token) return res.send('<a href="/login">Log in with UseThatApp</a>');
      const ent = await getEntitlement(token);
      res.json({ sub: req.session.utaSub, version: ent.version });
    });
    
    app.listen(3000);

    Step 5 — Gate on the Entitlement

    Anywhere you gate paid functionality, read the live plan and check the stable product_public_id (never the mutable display version). The entitlement is authoritative the moment a purchase completes — and the moment a license is canceled:

    Python

    any view
    from usethatapp import get_entitlement
    
    ent = get_entitlement(access_token)
    # Entitlement(entitled, version, product_public_id, status, is_free, period_end)
    if ent.entitled and ent.product_public_id == "prod_<your-pro-id>":
        unlock_pro_features()

    JavaScript

    any handler
    import { getEntitlement } from "usethatapp";
    
    const ent = await getEntitlement(accessToken);
    // { entitled, version, product_public_id, status, is_free, period_end }
    if (ent.entitled && ent.product_public_id === "prod_<your-pro-id>") {
      unlockProFeatures();
    }

    Step 6 — Create Your Products and Prices

    On the dashboard, create your products (tiers) and their prices — recurring or one-time. Each product gets the stable product_public_id your gates branch on, and each price gets an opaque public id (prc_…) plus a ready-made buy_url, both served by the Pricing API.

    Step 7 — Put Prices and Buy Buttons on Your Site

    Render your pricing page from the live pricing API — never hardcode prices — and point each Buy button at the hosted checkout. Buyers get one page for email + country + ZIP, card entry via Stripe, then land back in your app entitled:

    Python

    pricing view
    from usethatapp import get_prices, purchase_url
    
    plans = get_prices()   # live — no auth needed
    for p in plans.prices:
        # p.product_name, p.amount ("10.00"), p.currency ("usd"),
        # p.frequency ("month" or None), p.buy_url (hosted checkout)
        render_plan(p, buy=purchase_url(p.public_id,
                                        next="https://yourapp.com/welcome"))

    JavaScript

    pricing handler
    import { getPrices, purchaseUrl } from "usethatapp";
    
    const { prices } = await getPrices();   // live — no auth needed
    for (const p of prices) {
      // p.product_name, p.amount ("10.00"), p.currency ("usd"),
      // p.frequency ("month" or null), p.buy_url (hosted checkout)
      renderPlan(p, purchaseUrl(p.public_id,
                                { next: "https://yourapp.com/welcome" }));
    }

    next is optional — leave it off and buyers return to your Login URL. It must be HTTPS on your registered domain. The pricing API is also public and CORS-enabled, so a static marketing page can fetch it straight from the browser — see Sell from your own website.

    Step 8 — Test the Funnel, Then Go Live

    1. Run the seller walkthrough. Open your own buy link signed in as your developer account: same pages, a test banner, no Stripe calls, no charge — and it ends by setting your developer-preview entitlement (status: "preview") so you can verify the return into your app end to end. See Test your purchase flow.
    2. Check the readiness list on your app's manage page. A buy link resolves only while your seller account is verified, the app is published, the product is active, and external sales is on (the default); otherwise it returns 404.
    3. Go live. Buy links work for buyers in the United States, the European Union, Canada, Australia and the other countries offered at checkout.

    Next Steps

    That's the whole loop. To go deeper: Sell from your own website (purchasing), Licensing (entitlements), and Sign in with UseThatApp (identity and sign-out). Then pick your stack:

    AI agents: this site is fully pre-rendered — fetch /llms.txt for an indexed summary of every page, or /llms-full.txt for the whole documentation as one Markdown file.