Sscenelet
API & MONETIZATION

Build on Scenelet

Embed Scenelet into your own browser plugin, desktop app, or web tool. Your users sign in with their official Scenelet account, and every paid action is metered against their credit balance — the platform handles login, billing, payouts, and remote control, so you only build the feature.

Core concepts

Two identifiers and one token wire your app to the platform. They do different jobs — keep them straight.

app_key
Identifies one asset (your plugin/skill) for billing. Issued when you publish the asset; this is where its price lives. Sent on every execute call.
client_id
Identifies your OAuth app for the login flow. Created in the Developer apps dashboard. Public clients use PKCE only; confidential (server) clients also get a one-time secret.
Scenelet token
The per-user Bearer access token minted after login (valid 90 days by default). The SDK stores it for you; the gateway resolves it to the user whose credits get charged.

1. Get your credentials

You need two things before writing code.

Publish an asset → app_key

Submit your plugin/skill and set its pricing model (free, pay-per-use, subscription, or lifetime). On approval you get an app_key. The price you set here is the only price the gateway will ever charge.

Register an OAuth client → client_id

In the Developer apps dashboard, create a client and add your exact redirect URIs (web callback, a 127.0.0.1 loopback for desktop, or your extension's chromiumapp.org URL). Choose public (PKCE) for plugins and desktop apps, or confidential for a server that can keep a secret.

2. Install the SDK

Use the npm package in a bundler, or load the UMD build via a script tag for a plain extension or page — it exposes window.Scenelet.

# npm (bundlers)
npm install @scenelet/sdk

# CDN (plain <script>, exposes window.Scenelet)
<script src="https://api.scenelet.com/sdk/v1/scenelet-sdk.umd.js"></script>
import { Scenelet } from '@scenelet/sdk';

const sc = new Scenelet({
  appKey: 'app_xxxxxxx',   // the asset being billed
  clientId: 'cli_xxxxxxx', // your OAuth app (login flow)
});

3. Sign the user in

All three flows are Authorization Code + PKCE and end by minting and storing the user's token. Pick the one that matches your runtime.

Web page — popup

loginViaPopup opens the official login in a popup and resolves once approved. Your redirect page (on your own origin) just calls completePopupCallback().

// 1) trigger login (e.g. on a button click)
await sc.loginViaPopup({ redirectUri: 'https://yourapp.com/oauth/callback' });

// 2) on https://yourapp.com/oauth/callback :
Scenelet.completePopupCallback('https://yourapp.com');

Browser extension — chrome.identity

loginViaChromeIdentity drives the flow through chrome.identity. Register chrome.identity.getRedirectURL() as a redirect URI on your client.

// background / service worker / popup script
await sc.loginViaChromeIdentity();
// redirect URI to register: chrome.identity.getRedirectURL()

Desktop / server — manual

Build the authorize URL, open the system browser, capture the ?code on your loopback redirect, then exchange it. Confidential clients also pass their secret to exchangeCode().

const { url, codeVerifier, state } = await sc.buildAuthorizeUrl({
  redirectUri: 'http://127.0.0.1:8976/cb',
});
// open `url` in the system browser; capture ?code & ?state on your loopback

const token = await sc.exchangeCode({
  code, codeVerifier,
  redirectUri: 'http://127.0.0.1:8976/cb',
  clientSecret: 'csk_xxxxxxx', // confidential clients only
});

4. Gate every paid action

Call gate() right before a metered action. It phones the gateway and, on denial, shows the official topup / login modal for you — you render no billing UI. Use checkAuth() instead if you want to handle the result yourself.

if (!(await sc.gate())) return;   // SDK shows topup / login modal on denial
runMyPaidFeature();

// …or handle the result yourself:
const r = await sc.checkAuth();
if (!r.ok) showMyOwnUI(r.reason, r.message);

The execute API

Prefer the SDK, but the gate is a single HTTP call you can make from anywhere. POST the asset id as app_id with the user's Bearer token. feature_cost is advisory only — the gateway always charges the asset's server-configured price, so a client can never under-report to pay less.

Request
POST https://api.scenelet.com/v1/sdk/execute
Authorization: Bearer <scenelet-token>
Content-Type: application/json

{ "app_id": "app_xxxxxxx", "tier": "standard" }
200 — authorized
{
  "ok": true,
  "status": "authorized",
  "userId": "u_abc",
  "remaining_credits": 95,
  "runId": "run_xyz",
  "actualTier": "standard"
}
denied
{
  "ok": false,
  "status": "forbidden",
  "reason": "insufficient_funds",
  "message": "需要 50 点,当前 32 点",
  "topupUrl": "https://scenelet.com/topup"
}

ok:true means the credit was charged and the run is cleared; keep the runId. A 401 means the token is invalid or expired; 403 means a daily quota was hit; 429 means a per-minute flood limit — honor Retry-After.

Denial reasons

Every non-ok response carries a stable reason string. Handle these to give users the right next step — the SDK's modal already maps them.

reasonWhat it means
insufficient_fundsNot enough credits for this run — send the user to topup.
not_purchasedA subscription/lifetime asset the user hasn't bought yet.
subscription_expiredTheir subscription lapsed — prompt to renew.
plugin_disabledThe platform remotely disabled this asset (kill-switch).
bad_app_keyUnknown, unpublished, or malformed app_key.
no_user_tokenNo token, or it's invalid/expired — prompt login.
rate_limitedPer-minute flood limit hit (429) — retry after the header.
quota_exceededDaily tier quota exhausted (403).
network_errorThe SDK couldn't reach the gateway.

How billing works

You set a price; the platform collects credits from the user, splits the revenue, and pays you out. There is nothing to reconcile on your side.

Pricing models
  • Free — always allowed, no charge.
  • Pay-per-use — debits priceCredits from the user on each successful run.
  • Subscription — one upfront purchase, then unlimited runs until it expires.
  • Lifetime — one upfront purchase, unlimited runs forever.
Revenue split — creators keep 85%; the platform takes 15% (configurable per asset). Users buy credits with real money via Stripe; you're paid out from your earnings balance.
Premium tier — pass tier:"premium" to charge 2× for a heavier model run. Set allowDowngrade:true to transparently fall back to standard, in one call, when the user can't afford premium.
Server-authoritative pricing — the charge is always priceCredits × tier from the asset config. The client's feature_cost is ignored for billing, so a tampered client can't pay less.

Control & safety

The platform keeps levers over every integration — useful for you (abuse protection) and enforceable by us (policy).

  • Remote kill-switch — an asset can be disabled instantly; execute then returns plugin_disabled and the SDK shows a notice. No redeploy on your side.
  • Tiered quotas — anonymous < member < paid daily caps (admin-tunable). Anonymous users hitting the cap get trigger_auth_modal so you can prompt signup.
  • Flood protection — a per-minute limit returns 429 with Retry-After before the daily quota is even checked.
  • Token expiry — tokens last 90 days by default and resolve fresh on every call, so revoked or expired credentials stop working within seconds.
  • Refund-safe — the charge commits when execute returns ok. If the platform's execution backend fails downstream, it reverses the debit via an internal endpoint; you don't call it yourself.

Security notes

  • PKCE is S256-only — the plain method is rejected.
  • redirect_uri must exactly match one registered on the client — no wildcards.
  • Authorization codes are single-use and expire in 60 seconds.
  • Confidential client secrets are shown once and stored hashed — rotate by creating a new client.
Build on Scenelet · Scenelet