settlemesh-sdk
v0.4.0
Published
Add SettleMesh login, database and payments to any app — any framework, hosted anywhere.
Maintainers
Readme
settlemesh-sdk
Add SettleMesh login, database and payments to any app — any framework, hosted anywhere (your own server, Vercel, Cloudflare, Render, a Raspberry Pi). You write the routes and decide where the "Sign in with SettleMesh" entry point lives; the SDK does the protocol work.
It is not tied to SettleMesh hosting. Drop it into an Express/Hono/Fastify/Next/SvelteKit/plain-Node app and call three small APIs.
npm install settlemesh-sdkRuns anywhere with the Web Crypto API: Node 18+, Cloudflare Workers, Deno, Bun, the browser. Zero dependencies. Ships with TypeScript types, and transparently retries transient gateway errors (502/503/504) on idempotent reads — never on writes/charges.
Get credentials
npx settlemesh apps register \
--name "My App" \
--redirect-uri https://myapp.com/auth/callback \
--with-database --with-paymentThis prints (and never needs a SettleMesh-hosted deploy):
| Pillar | You receive | Use it for |
|----------|------------------------------------------|------------|
| Login | clientId | OAuth — auth.* |
| Database | projectId, serverKey | SQL — db.* (server-side only) |
| Payments | merchantKey, webhookSecret | Checkout — payments.* (server-side only) |
Create a client
import { createClient } from "settlemesh-sdk";
const settle = createClient({
clientId: process.env.SETTLEMESH_CLIENT_ID,
projectId: process.env.SETTLEMESH_PROJECT_ID,
serverKey: process.env.SETTLEMESH_SERVER_KEY,
merchantKey: process.env.SETTLEMESH_MERCHANT_KEY,
webhookSecret: process.env.SETTLEMESH_WEBHOOK_SECRET,
sessionSecret: process.env.SESSION_SECRET, // for durable login cookies
});Pass only the credentials for the pillars you use.
Login — you choose where it lives
The SDK gives you a URL and the identity; you own the two routes. Example with plain Node, but the same two calls work in any framework:
// 1) Your "Sign in" route — wherever you want it
const { url, state, codeVerifier } = await settle.auth.createAuthorizationUrl({
redirectUri: "https://myapp.com/auth/callback",
});
// stash { state, codeVerifier } in a short-lived signed/HttpOnly cookie, then redirect:
res.writeHead(302, { Location: url });
// 2) Your callback route — path is your choice, just match the redirectUri
const { user, idToken } = await settle.auth.handleCallback({
code, redirectUri: "https://myapp.com/auth/callback", codeVerifier,
});
// user = { sub, email, name, ... }Keep users signed in with your own durable cookie (decoupled from the 15‑minute OAuth token):
const sessionCookie = await settle.auth.createSession(user, { maxAgeSec: 60 * 60 * 24 * 7 });
// later, on any request:
const me = await settle.auth.readSession(cookieValue); // null if absent/expired/tamperedHelpers parseCookies(header) and serializeCookie(name, value, opts) are exported so you never hand-roll cookie strings.
Database
await settle.db.migrate("init", `
create table if not exists notes (id integer primary key, user_id text, body text)
`);
await settle.db.query("insert into notes (user_id, body) values (?, ?)", [user.sub, "hi"]);
const { rows } = await settle.db.query("select * from notes where user_id = ?", [user.sub]);
const one = await settle.db.queryOne("select count(*) as n from notes");Server-side only — never expose serverKey to the browser. Params are positional (? for SQLite/D1, $1 for Postgres/Neon).
Payments
// Start a checkout, redirect the user to `url`
const checkout = await settle.payments.createCheckout({
amount: 50, // credits
description: "Pro plan",
externalId: order.id,
returnUrl: "https://myapp.com/billing/return",
cancelUrl: "https://myapp.com/billing",
idempotencyKey: order.id,
});
res.writeHead(302, { Location: checkout.url });Confirm completion either way:
// (a) On your return route — poll:
const c = await settle.payments.retrieveCheckout(checkoutId);
if (c.paid) { /* fulfil */ }
// (b) Via webhook (recommended) — verify the signature against the RAW body:
const event = await settle.payments.verifyWebhook({
payload: rawBody, // the raw string, do NOT re-serialize
signature: req.headers["x-settlemesh-signature"],
});
if (event.event === "checkout.completed") { /* fulfil event.data.external_id */ }Runtime — capabilities & storage, billed to the END USER
For a deployed app's backend: call official capabilities (LLM, search, scrape, video) and object
storage with the app's runtime key (SETTLEMESH_APP_API_KEY, injected for you). By default these bill the
developer who owns the key. To bill the logged-in user for their own usage instead, pass that user's
token as payer — extractPayerToken(req) pulls it off the incoming request in one line:
import { createClient, extractPayerToken } from "settlemesh-sdk";
const settle = createClient({}); // apiKey defaults to process.env.SETTLEMESH_APP_API_KEY
app.post("/ai/summarize", async (req, res) => {
const payer = extractPayerToken(req); // the logged-in user's token (Bearer or __settle_session cookie)
const out = await settle.runtime.invokeCapability("llm.generate",
{ prompt: req.body.text }, { payer }); // ← the USER pays for their own call, not you
res.json(out);
});No payer? The key owner (you) pays — never a surprise charge to the wrong account. Read your app's own
non-secret config (storage / capability / database URLs, app id) at runtime instead of baking it in:
const cfg = await settle.runtime.config(); // { base_url, storage_api, capabilities_invoke, app_id, ... }API surface
createClient(cfg)→{ auth, db, payments, runtime, baseUrl }- auth:
createAuthorizationUrl,handleCallback,getUser,decodeIdToken,createSession,readSession - db:
query,queryOne,migrate - payments:
createCheckout,retrieveCheckout,verifyWebhook - runtime:
invokeCapability(id, input, { payer }),storagePut/storageGet(key, { payer }),config()— plusextractPayerToken(req)
All errors are SettleMeshError with .status, .code, .details.
Self-hosting / different base URL
createClient({ baseUrl: "https://settlemesh.example.com", /* ... */ });Defaults to https://www.settlemesh.io.
