@ambasada/sdk
The Ambasada platform client: Sign in with Ambasada, mini-app notifications, the project store, files. Node ≥ 22, Bun, Cloudflare Workers, Next.js route handlers. No runtime dependencies.
import { ambasada } from "@ambasada/sdk"; // AMBASADA_API_KEY, AMBASADA_API_URL (default https://api.ambasada.dev)
ambasada.auth.login(req) / .callback(req) / .user(req) / .logout(req) / .shell(req) | Sign in with Ambasada (below) |
ambasada.apps.verifyWebhook(req) / .notify({ … }) | the Ambasada app's events and push notifications (docs/MINI-APPS.md §7) |
ambasada.store.get(key) / .put(key, value) / .delete(key) / .list(prefix) | JSON key-value, 1 MB per app |
ambasada.files.put(data) | a file at a public URL |
ambasada.project(), ambasada.keys() | the app's id; the API's signing keys |
defineApp({ id, name, description, product }) | ambasada.config.ts |
Errors are AmbasadaError with code from @ambasada/protocol (isAmbasadaError(e, "UNAUTHORIZED")), and
WebhookInvalidError (400). Workers: createAmbasada({ env }). An app's own database, KV, files and Durable
Objects are Worker bindings (env.DB, env.KV, env.FILES; docs/STORAGE.md), not SDK calls.
The Hack A Ton economy (TON, checkout, offerings, the agent) is the add-on
@ambasada/village. Its 0.7 names (village, createVillage,
VillageError, offering, defineCompany, …) are still exported here, @deprecated, until 2.0. Note that
createAmbasada returns the platform client from 0.8; selling code uses createVillage.
Sign in with Ambasada
People sign in to your product with their Ambasada ID (OAuth 2.0 code flow + PKCE, consent screen). Zero
setup on https://<slug>.ambasada.dev: ambasada deploy injects AMBASADA_AUTH_CLIENT_ID,
AMBASADA_AUTH_CLIENT_SECRET and AMBASADA_AUTH_ISSUER; ambasada dev injects fake ones and its mock signs you in
as "Fake Visitor". Fetch-style handlers for Workers and Next.js route handlers, WebCrypto only.
const ambasada = createAmbasada({ env });
// GET /api/auth/login?returnTo=/path → redirect to the Ambasada ID consent screen
return ambasada.auth.login(req, { returnTo: "/", email: true }); // email: also ask for their email
// GET /api/auth/callback → checks state, exchanges the code, reads the profile, sets the cookie
return ambasada.auth.callback(req);
// anywhere on the server
const user = await ambasada.auth.user(req); // AmbasadaUser | null
// GET /api/auth/logout
return ambasada.auth.logout(req, { returnTo: "/" });
AmbasadaUser is { id, name, avatar_url, builder, builder_since, member_since, pass, email? }: id is the
stable Ambasada ID (user_…), pass is "builder" | "visitor" | "staff" | null for the current event, email
only if you asked and they agreed. The session is an HttpOnly SameSite=Lax cookie (__Host-ambasada_session
on https) holding the signed profile (HMAC-SHA256, key derived from the client secret), valid 7 days; no token is
kept. returnTo must be a path on your site. user() also takes { headers } (Next server components:
ambasada.auth.user({ headers: await headers() })). ambasada.auth.configured says whether credentials are set.
Override them with createAmbasada({ auth: { clientId, clientSecret, issuer, publicUrl } }); PUBLIC_URL sets the
origin of the callback URL when the Worker sees another host. More: docs/AUTH.md.
pnpm build writes dist/ (one ESM file + .d.ts); publishConfig points the published package at it.