# Getting Started # Getting Started AscendKit gives your app authentication, analytics, email templates, surveys, and user journeys — configured from the CLI or AI coding agents, not hardcoded. ## Framework support | Framework | Auth | Components | Analytics | Webhooks | |-----------|------|------------|-----------|----------| | **Next.js 14+** | Full (server + client) | Pre-built UI | Client + server | Server | | **React (Vite, Remix, Astro)** | Client hooks only | Pre-built UI | Client only | — | | **FastAPI** | Token verification | — | Server | Server | | **Flask / Django** | Token verification | — | Server | Server | | **Any Python** | Token verification | — | Server | Server | The JavaScript SDK package is `@ascendkit/nextjs`. React hooks and components work in any React framework, but the server-side auth runtime (session management, OAuth callbacks) requires Next.js today. Python backends use the `ascendkit` package for token verification and webhooks with any framework. ## Setup sequence Follow these steps in order: ### 1. Create an account Sign up at the [AscendKit portal](/). When you sign up, AscendKit creates your first project with a **dev** environment pre-loaded with starter content: - An **onboarding journey** with a welcome flow and common lifecycle triggers - **Email templates** for verification, welcome, and password reset - A **starter NPS survey** Your dev environment is fully functional with no restrictions — use it for local testing or ship it to customers as-is. Review and personalize the starter content whenever you're ready. ### 2. Install the CLI and SDK See [Installation](/docs/install) for OS-specific instructions and prerequisites. ### 3. Initialize the CLI In your app's root directory: ```bash ascendkit init ``` This opens a browser window for authentication and saves credentials locally. Then link to your development environment: ```bash ascendkit set-env pk_dev_your_public_key ``` This writes your API URL, public key, and secret key into `.env`/`.env.local`, and includes an `APP_URL=` placeholder. We recommend setting it to your app's origin (e.g., `APP_URL=http://localhost:3000`) to avoid social login callback issues. See [CLI Setup](/docs/cli) for details. ### 4. Review & configure Your dev environment comes with starter content that works out of the box. Use the CLI or portal to review and personalize it — no code changes needed. #### Review your starter content - [Email Templates](/docs/cli/templates) — customize the verification, welcome, and password reset emails for your brand - [User Journeys](/docs/cli/journeys) — adjust triggers and content in the onboarding journey to match your product's flow - [Surveys](/docs/cli/surveys) — edit the starter NPS survey or create CSAT and custom surveys #### Set up your own identity Starter defaults use AscendKit's shared infrastructure so you can get started immediately. Before going to production, set up your own: - **Email sending domain** — Starter emails send from AscendKit's address. To send as your brand, [verify a custom domain](/docs/cli/email) (DKIM/SPF) and create email identities for your sender addresses. - **OAuth credentials** — Social logins (Google, GitHub, LinkedIn) work immediately through AscendKit's shared OAuth proxy. For production, we strongly recommend [registering your own OAuth apps](/docs/cli/auth) with your own client ID and secret — this gives you full control over consent screens, branding, and rate limits. - [Webhooks](/docs/cli/webhooks) — configure endpoints to receive user lifecycle events in your backend - [Analytics](/docs/analytics) — track every visitor from first page view through signup with automatic identity stitching and attribution - [Analytics CLI](/docs/cli/analytics) — query visitor traffic, acquisition sources, and conversion metrics from the terminal Configuration is live — you can update templates, journeys, surveys, and auth settings at any time without redeploying your app. ### 5. Integrate with your app Add the SDK to your codebase. The setup differs by framework: - **Next.js**: [Next.js Integration](/docs/integration) — auth runtime, provider, pre-built UI components - **Python backend**: [Python SDK](/docs/python) — token verification, webhook handling ## Adding environments Your dev environment is all you need to get started — it's fully functional for both local development and production use. When you're ready to separate environments (e.g., a dedicated production environment with its own data and keys), use the **promote** command: ```bash ascendkit environment promote --target prod ``` This copies all your configuration, templates, journeys, and surveys into the new environment. The only change in your app is swapping the environment key — replace your `pk_dev_...` key with the new `pk_prod_...` key, and everything works the same way. Multiple environments are available on the [Launch and Scale tiers](/pricing). ## Architecture ``` Your App (Next.js / React / Python) └── AscendKit SDK └── AscendKit Backend API ├── Auth (credentials, OAuth, magic link, waitlist) ├── Analytics (pageviews, attribution, identity stitching) ├── Email (templates, domain verification) ├── Surveys (NPS, CSAT, custom) └── Journeys (lifecycle automation) CLI / Portal └── AscendKit Backend API (same) ``` The SDK runs inside your app and talks to the AscendKit backend. The CLI and portal configure that same backend. Your app code doesn't change when you toggle providers, update templates, or create surveys. ## AI-assisted integration If you're using an AI coding assistant (Cursor, Copilot, Claude, ChatGPT, etc.), point it at the machine-readable docs: - **[llms.txt](/docs/llms.txt)** — quickstart with code examples, plus links to each doc page. Best for most AI tools. - **[llms-full.txt](/docs/llms-full.txt)** — all documentation in a single file. Use when your AI has a large context window or you want the complete reference. --- # Installation # Installation ## Prerequisites - **Node.js** 18 or later (for CLI and JavaScript SDK) - **Python** 3.10 or later (for Python SDK, if applicable) ## CLI The CLI authenticates you, initializes projects, and configures all AscendKit services. Works on macOS, Linux, and Windows. ```bash npm install -g @ascendkit/cli ``` Or run without installing: ```bash npx @ascendkit/cli ``` ### Verify installation ```bash ascendkit --version ``` ## Next.js SDK For Next.js applications with full server-side auth, pre-built UI components, and analytics: ```bash npm install @ascendkit/nextjs ``` > **Note**: The package is `@ascendkit/nextjs`. It has `next >= 14` and `react >= 18` as peer dependencies. The React hooks and components also work in other React frameworks (Vite, Remix, Astro), but the server-side auth runtime requires Next.js. ## Python SDK For Python backends that need to verify access tokens or handle webhooks. Works with any framework (FastAPI, Flask, Django, etc.): ```bash pip install ascendkit ``` ## Environment variables After running `ascendkit init` and `ascendkit set-env`, the CLI writes these into your `.env` / `.env.local` files automatically. You can also set them manually: | Variable | Side | Required for | Description | |----------|------|-------------|-------------| | `ASCENDKIT_API_URL` | Server | All | AscendKit backend URL | | `NEXT_PUBLIC_ASCENDKIT_API_URL` | Client | Next.js | Same URL, exposed to browser | | `ASCENDKIT_ENV_KEY` | Server | All | Project public key (`pk_dev_...`) | | `NEXT_PUBLIC_ASCENDKIT_ENV_KEY` | Client | Next.js | Same key, exposed to browser | | `APP_URL` | Server | Auth redirects | Public URL where your app is hosted | | `ASCENDKIT_SECRET_KEY` | Server | Analytics, admin | Secret key (never expose to client) | | `ASCENDKIT_WEBHOOK_SECRET` | Server | Webhooks | Webhook signing secret (`whsec_...`) | `APP_URL` should be your app's origin — `http://localhost:3000` for local development, or `https://app.yourdomain.com` for production. The SDK can infer the origin when blank, but setting it explicitly avoids "Invalid origin" errors with social login. The `NEXT_PUBLIC_` prefixed variables are only needed for Next.js applications. Python backends only need `ASCENDKIT_ENV_KEY`, `ASCENDKIT_SECRET_KEY`, and `ASCENDKIT_WEBHOOK_SECRET`. ## Next steps 1. [Initialize the CLI](/docs/cli) in your project 2. [Integrate with your app](/docs/integration) (Next.js) or [set up your Python backend](/docs/python) --- # Next.js Integration # Next.js Integration This guide covers the `@ascendkit/nextjs` SDK with Next.js 14+ (App Router). For Python backends, see [Python SDK](/docs/python). For other React frameworks, see [Other React Frameworks](#other-react-frameworks) at the bottom of this page. ## Auth setup After [installing the SDK](/docs/install) and [initializing the CLI](/docs/cli), follow these four steps: ### 1. Create the auth runtime ```ts // lib/auth.ts import { createAscendKitAuthRuntime } from "@ascendkit/nextjs/server"; export const authRuntime = createAscendKitAuthRuntime(); ``` The runtime reads `ASCENDKIT_ENV_KEY`, `ASCENDKIT_SECRET_KEY`, and `ASCENDKIT_API_URL` from your environment automatically. We recommend setting `APP_URL` in your env file to your app's origin (e.g., `http://localhost:3000` for local dev) — the SDK can infer it from requests, but an explicit value avoids "Invalid origin" errors with social login. If you prefer explicit configuration: ```ts export const authRuntime = createAscendKitAuthRuntime({ publicKey: process.env.ASCENDKIT_ENV_KEY!, }); ``` #### Options | Option | Type | Description | |--------|------|-------------| | `publicKey` | `string` | Environment public key (default: `ASCENDKIT_ENV_KEY` env var) | | `secretKey` | `string` | Environment secret key (default: `ASCENDKIT_SECRET_KEY` env var) | | `authSecret` | `string` | Better Auth session secret (default: `BETTER_AUTH_SECRET`, then `AUTH_SECRET`, then `ASCENDKIT_SECRET_KEY`) | | `apiUrl` | `string` | AscendKit API URL (default: `ASCENDKIT_API_URL` or `https://api.ascendkit.dev`) | | `appBaseUrl` | `string` | Public app origin used for auth callbacks and redirects | | `allowedHosts` | `string[]` | Optional allowlist for dynamic callback host resolution | | `trustedProxyHeaders` | `boolean` | Trust `x-forwarded-*` headers when deriving the request origin | | `waitlistRedirectPath` | `string` | Redirect path for waitlisted users after social login (e.g. `"/waitlist"`) | | `rejectedRedirectPath` | `string` | Redirect path for rejected users after social login (e.g. `"/rejected"`) | Example with waitlist redirect: ```ts export const authRuntime = createAscendKitAuthRuntime({ waitlistRedirectPath: "/waitlist", rejectedRedirectPath: "/rejected", }); ``` If your app has a fixed public URL, configure it through AscendKit: ```ts export const authRuntime = createAscendKitAuthRuntime({ appBaseUrl: process.env.APP_URL, }); ``` `APP_URL` should be the origin where your app is running — `http://localhost:3000` for local development, or `https://app.yourdomain.com` for production. You can leave it blank and let the SDK infer the origin from the request, but an explicit value is more reliable. If you use preview deployments, allow dynamic hosts explicitly: ```ts export const authRuntime = createAscendKitAuthRuntime({ allowedHosts: ["localhost:3000", "*.vercel.app", "app.example.com"], }); ``` When a user signs up via social login (Google, GitHub, etc.) and lands on the waitlist, they are redirected to `waitlistRedirectPath` instead of receiving a `?error=waitlist_pending` query param. The same applies to rejected users. If these options are not set, the default behavior appends `?error=waitlist_pending` or `?error=waitlist_rejected` to the callback URL. ### 2. Add the API route ```ts // app/api/auth/[...all]/route.ts import { authRuntime } from "@/lib/auth"; import { createAuthRouteHandlers } from "@ascendkit/nextjs/server"; export const { GET, POST } = createAuthRouteHandlers(authRuntime); ``` ### 3. Add the provider ```tsx // app/layout.tsx import { AscendKitProvider } from "@ascendkit/nextjs"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` The provider reads `NEXT_PUBLIC_ASCENDKIT_ENV_KEY` and `NEXT_PUBLIC_ASCENDKIT_API_URL` from the environment. If you prefer explicit configuration: ```tsx ``` ### 4. Add sign-in and sign-up `` already mounts a global auth modal for you — you don't need to render one yourself. You have three UX patterns to choose from. Pick the one that fits your app; you can mix them. #### Which option should I use? | Scenario | Recommended option | |----------|-------------------| | Marketing site or landing page — user clicks "Sign in" in the header and stays on the page | **Modal** — `` / `` (default `mode="modal"`) | | Dedicated `/login` and `/signup` routes with the same styling everywhere | **Dedicated page (all-in-one)** — `` | | Fully custom layouts or marketing content next to a login form | **Dedicated page (split)** — `` and `` | | App with a custom nav button that should open auth without navigating | **Hook** — `useAuthModal()` | | Gate protected content until the user signs in | Wrap with `` / `` and trigger the modal from the fallback | #### Option A — Modal (no auth page needed) Drop buttons anywhere in your layout. `AscendKitProvider` handles everything else. ```tsx // app/layout.tsx (inside the provider) import { SignInButton, SignUpButton, SignedIn, SignedOut, AscendKitUserButton } from "@ascendkit/nextjs";
``` Want to use your own button component? Pass `asChild`: ```tsx import { Button } from "@/components/ui/button"; ``` Need to open the modal programmatically (e.g. after a pricing CTA or analytics event)? Use the hook: ```tsx import { useAuthModal } from "@ascendkit/nextjs"; function UpgradeCta() { const { open } = useAuthModal(); return ; } ``` #### Option B — Dedicated page with `AscendKitAuthCard` Use this when you want a real `/login` route (e.g. for SEO, email deep links, or password managers). One component handles sign-in, sign-up, and forgot-password views. ```tsx // app/login/page.tsx import { AscendKitAuthCard } from "@ascendkit/nextjs"; export default function AuthPage() { return ; } ``` Pin a specific view if you have separate routes for sign-in and sign-up: ```tsx // app/signup/page.tsx ``` Views: `"SIGN_IN"`, `"SIGN_UP"`, `"FORGOT_PASSWORD"`. #### Option C — Separate `Login` / `SignUp` components Use this when you want full control of each page (e.g. marketing copy beside the form, different layouts for sign-in vs sign-up). ```tsx // app/login/page.tsx import { Login } from "@ascendkit/nextjs"; export default function LoginPage() { return ( { window.location.href = "/dashboard"; }} onSwitchToSignUp={() => { window.location.href = "/signup"; }} onForgotPassword={() => { window.location.href = "/forgot-password"; }} /> ); } ``` ```tsx // app/signup/page.tsx import { SignUp } from "@ascendkit/nextjs"; export default function SignUpPage() { return ( { window.location.href = "/dashboard"; }} onSwitchToLogin={() => { window.location.href = "/login"; }} /> ); } ``` > **Heads up:** you don't need ``, ``, or `` if you're only using `SignInButton` / `SignUpButton` / `useAuthModal()` — the provider's built-in modal already renders the forms. Pick one pattern per route and stick with it. ## Components All components render inside ``. Social buttons appear automatically when providers are enabled via the CLI or portal. ### Login / SignUp | Prop | Type | Description | |------|------|-------------| | `onSuccess` | `() => void` | Called after success | | `onError` | `(error: string) => void` | Called on failure | | `callbackURL` | `string` | Redirect after social auth | | `onSwitchToSignUp` | `() => void` | (Login) Show "Sign Up" link in footer | | `onSwitchToLogin` | `() => void` | (SignUp) Show "Sign In" link in footer | | `onForgotPassword` | `() => void` | (Login) Show "Forgot password?" link | | `onBackToLogin` | `() => void` | (ForgotPassword) Show "Back to sign in" link | ### AscendKitAuthCard Single component for all auth views. Use instead of separate Login/SignUp pages. ```tsx import { AscendKitAuthCard } from "@ascendkit/nextjs"; ``` Views: `"SIGN_IN"`, `"SIGN_UP"`, `"FORGOT_PASSWORD"` ### SocialButton ```tsx import { SocialButton } from "@ascendkit/nextjs"; ``` ### AscendKitUserButton User avatar with sign-out dropdown for your app header or sidebar. ```tsx import { AscendKitUserButton } from "@ascendkit/nextjs"; ``` Top nav: ```tsx ``` Expanded vertical nav: ```tsx ``` Collapsed vertical nav: ```tsx ``` ### SignInButton / SignUpButton Convenience buttons that open the auth modal or redirect to an auth page. ```tsx import { SignInButton, SignUpButton } from "@ascendkit/nextjs"; ``` | Prop | Type | Default | Description | |------|------|---------|-------------| | `mode` | `"modal" \| "redirect"` | `"modal"` | Open modal or redirect | | `redirectUrl` | `string` | `"/auth/sign-in"` or `"/auth/sign-up"` | Path for redirect mode | | `asChild` | `boolean` | `false` | Delegate rendering to child element | | `children` | `ReactNode` | `"Sign in"` / `"Sign up"` | Button label | #### Using `asChild` with your own components When `asChild` is true, the button delegates its `onClick` handler to the child element instead of wrapping in its own ` ``` #### Using the `useAuthModal` hook directly For full control (e.g. combining analytics with auth), use the hook instead: ```tsx import { useAuthModal } from "@ascendkit/nextjs"; import { Button } from "@/components/ui/button"; function NavAuth() { const { open } = useAuthModal(); return ( ); } ``` ### SignedIn / SignedOut / AuthLoading ```tsx import { SignedIn, SignedOut, AuthLoading } from "@ascendkit/nextjs";

Loading...

Sign in ``` ## Hooks ### useAscendKit ```tsx const { user, isLoaded, isAuthenticated, signOut } = useAscendKit(); ``` | Return | Type | Description | |--------|------|-------------| | `user` | `User` or `null` | Current user | | `isLoaded` | `boolean` | Auth state resolved | | `isAuthenticated` | `boolean` | Signed in | | `signOut` | `() => Promise` | Sign out | ### useAuthModal ```tsx const { isOpen, view, open, close } = useAuthModal(); ``` | Return | Type | Description | |--------|------|-------------| | `isOpen` | `boolean` | Modal open | | `view` | `"sign-in"` or `"sign-up"` | Current view | | `open` | `(view?) => void` | Open modal | | `close` | `() => void` | Close modal | ## Access Tokens (frontend → backend) If your app has a separate backend (Python, Node.js, etc.), use access tokens to authenticate API calls from the browser. AscendKit issues short-lived RS256 JWTs that any backend can verify using JWKS — no shared secrets needed. ### 1. Add the access token route ```ts // app/api/access-token/route.ts import { authRuntime } from "@/lib/auth"; import { createAccessTokenHandler } from "@ascendkit/nextjs/server"; export const GET = createAccessTokenHandler({ authRuntime }); ``` This exchanges the user's session cookie for a short-lived access token. The route must be in the same Next.js app as your auth runtime. ### 2. Get tokens from React ```tsx import { useAccessToken } from "@ascendkit/nextjs"; function Dashboard() { const { getAccessToken } = useAccessToken(); async function fetchData() { const token = await getAccessToken(); const res = await fetch("https://api.example.com/data", { headers: { Authorization: `Bearer ${token}` }, }); return res.json(); } return ; } ``` `getAccessToken()` caches the token in memory and automatically refreshes it when it nears expiry (within 5 minutes). Concurrent calls are deduplicated — only one network request is made. ### 3. Verify on your backend The access token is a standard RS256 JWT. Verify it using AscendKit's JWKS endpoint with any JWT library, or use the Python SDK: ```python from ascendkit import AccessTokenVerifier verifier = AccessTokenVerifier() claims = await verifier.verify_async(token) print(claims["sub"]) # usr_... print(claims["email"]) ``` See [Python SDK](/docs/python) for FastAPI, Flask, and Django integration examples. ### useAccessToken ```tsx const { getAccessToken } = useAccessToken(); const token = await getAccessToken(); ``` | Return | Type | Description | |--------|------|-------------| | `getAccessToken` | `() => Promise` | Returns a cached or fresh access token | Throws if no authenticated session exists. Pass a custom path if your route isn't at `/api/access-token`: ```tsx const { getAccessToken } = useAccessToken("/api/custom-token-path"); ``` ## Auth Lifecycle AscendKit handles email verification and waitlist flows automatically. The SDK shows in-modal notifications for each state transition — no extra pages or code required. ### Default behavior (zero-config) **Credentials (email/password):** | Event | What the user sees | |-------|-------------------| | Signup (verification enabled) | Success banner: "Account created! Please check your email to verify your account." | | Signup (waitlist enabled) | Success banner: "You are on the waitlist. We will notify you when your account is approved." | | Signup (rejected) | Error banner: "Your account application was not approved." | | Login (email not verified) | Error banner: "Email not verified" | | Login (waitlisted) | Error banner: "Your account is pending approval." | | Login (rejected) | Error banner: "Your account application was not approved." | **Email verification redirect:** | Event | What the user sees | |-------|-------------------| | Clicks verification link | Redirected to your app with modal open + banner: "Email verified! You can now sign in." | | Clicks verification link (waitlist enabled) | Redirected to your app with info banner: "Email verified! Your account is pending approval. We'll notify you when it's ready." Sign-in modal does **not** open. | **Social login (Google, GitHub, etc.):** | Event | What the user sees | |-------|-------------------| | Social login success | Signed in and redirected to `callbackURL` | | Social login (waitlisted, `waitlistRedirectPath` set) | Redirected to `waitlistRedirectPath` (e.g. `/waitlist`) | | Social login (waitlisted, no redirect path) | Redirected to `callbackURL` with `?error=waitlist_pending` | | Social login (rejected, `rejectedRedirectPath` set) | Redirected to `rejectedRedirectPath` (e.g. `/rejected`) | | Social login (rejected, no redirect path) | Redirected to `callbackURL` with `?error=waitlist_rejected` | **Magic link:** | Event | What the user sees | |-------|-------------------| | Magic link submit | Banner: "Check your email for the sign-in link." | | Clicks magic link | User is signed in directly — no verification or password required. | All credential and magic link flows work identically whether the user is signing in through the provider-managed modal (`SignInButton` / `useAuthModal`), ``, or the split `` / `` components. Social login redirects are handled server-side by the OAuth proxy plugin — see [auth runtime options](#options) for `waitlistRedirectPath` and `rejectedRedirectPath`. ### Custom override: notification handling Read `authNotification` from context to render messages in your own UI: ```tsx import { useAscendKitContext } from "@ascendkit/nextjs"; function MyCustomAuthPage() { const { authNotification, clearAuthNotification } = useAscendKitContext(); // { variant: "success" | "error" | ..., message: string } or null if (authNotification) { return (

{authNotification.message}

); } return ; } ``` ### Custom override: email verification landing page After clicking the verification link, the user is redirected with `?verified=true`. If waitlist is also enabled, the URL includes `?verified=true&waitlisted=true`. The SDK auto-detects these params and shows the appropriate banner. To handle it yourself instead, create a page that reads the query params: ```tsx // app/verified/page.tsx "use client"; import { useSearchParams } from "next/navigation"; import { useAuthModal } from "@ascendkit/nextjs"; export default function VerifiedPage() { const params = useSearchParams(); const { open } = useAuthModal(); const verified = params.get("verified") === "true"; const waitlisted = params.get("waitlisted") === "true"; if (verified && waitlisted) { return (

Email verified!

Your account is pending approval. We'll notify you when it's ready.

); } if (verified) { return (

Email verified!

Your email has been confirmed.

); } return null; } ``` ### Custom override: waitlist gating After login, check `user.waitlistStatus` to gate access: ```tsx import { useAscendKit } from "@ascendkit/nextjs"; function Dashboard() { const { user, isLoaded } = useAscendKit(); if (!isLoaded) return

Loading...

; if (user?.waitlistStatus === "pending") { return (

You're on the waitlist

We'll notify you when your account is approved.

); } if (user?.waitlistStatus === "rejected") { return

Your application was not approved.

; } return ; } ``` ### Signals reference | Signal | Where | Type | |--------|-------|------| | `authNotification` | `useAscendKitContext()` | `{ variant, message } \| null` | | `clearAuthNotification` | `useAscendKitContext()` | `() => void` | | `?verified=true` | URL query param | Auto-detected on mount | | `?waitlisted=true` | URL query param | Auto-detected on mount (with `?verified=true`) | | `user.emailVerified` | `useAscendKit()` | `boolean` | | `user.waitlistStatus` | `useAscendKit()` | `"pending" \| "approved" \| "rejected" \| undefined` | ## Theming & Dark Mode AscendKit auth UI adapts to dark mode automatically with no configuration. ### How it works 1. **Tailwind class-based** (preferred): If your app sets `class="dark"` on ``, all AscendKit components switch to dark mode. 2. **System preference fallback**: If no Tailwind dark class is detected, components follow the user's OS `prefers-color-scheme` setting. Both modes work simultaneously. Tailwind class takes priority when present. ### CSS custom properties Override these variables on any ancestor element to match your brand. **Modal:** | Variable | Light default | Dark default | |----------|--------------|--------------| | `--ak-modal-bg` | `#fff` | `#0a0a0a` | | `--ak-modal-color` | `#1a1a1a` | `#f5f5f5` | | `--ak-modal-muted` | `#888` | `#888` | | `--ak-modal-hover-bg` | `rgba(0,0,0,0.05)` | `rgba(255,255,255,0.1)` | **Notification banners:** | Variable | Light default | Dark default | |----------|--------------|--------------| | `--ak-notif-success-bg` | `#f0fdf4` | `rgba(34,197,94,0.1)` | | `--ak-notif-success-color` | `#166534` | `#86efac` | | `--ak-notif-success-border` | `#bbf7d0` | `rgba(34,197,94,0.2)` | | `--ak-notif-error-bg` | `#fef2f2` | `rgba(239,68,68,0.1)` | | `--ak-notif-error-color` | `#991b1b` | `#fca5a5` | | `--ak-notif-error-border` | `#fecaca` | `rgba(239,68,68,0.2)` | ### Example: custom brand colors ```css :root { --ak-modal-bg: #fafaf9; --ak-notif-success-bg: #ecfdf5; --ak-notif-success-color: #065f46; } html.dark { --ak-modal-bg: #111111; --ak-notif-success-bg: rgba(16, 185, 129, 0.1); --ak-notif-success-color: #6ee7b7; } ``` ### Auth form styling The auth forms (sign-in, sign-up, forgot password) are rendered by [Better Auth UI](https://github.com/daveyplate/better-auth-ui). They inherit your app's Tailwind theme and can be styled via its theming API. AscendKit components use `ak-` prefixed CSS classes so they never conflict with your existing styles. ## Analytics ### Client-side (React) Track user events from the browser. Browser context (userAgent, locale, screen size, referrer, URL) is captured automatically. Requires the user to be authenticated. ```tsx import { useAnalytics } from "@ascendkit/nextjs"; function CheckoutButton() { const { track, flush } = useAnalytics(); return ( ); } ``` Events batch in memory and flush every 30 seconds, when the batch fills (10 events), or on page hide. `flush()` returns a promise, so `await flush()` when you need the queued events sent before navigation or other follow-up work. ### Server-side (Node.js) Track events from your backend with trusted identity (secret key auth). ```ts import { Analytics } from "@ascendkit/nextjs/server"; const analytics = new Analytics(); analytics.track("usr_456", "checkout.completed", { total: 99.99 }); ``` The constructor reads `ASCENDKIT_SECRET_KEY` and `ASCENDKIT_ENV_KEY` from your environment. If either is missing, it throws at initialization. Events batch in memory and flush every 30 seconds, when the batch fills (10 events), or on `shutdown()`. Call `analytics.shutdown()` for graceful cleanup (e.g. in `process.on("beforeExit")`). See [Analytics](/docs/analytics) for the full guide including event naming, Python server-side tracking, and how analytics powers journey automations. ## Other React Frameworks The React hooks and components from `@ascendkit/nextjs` work in any React 18+ framework (Vite, Remix, Astro, etc.): - ``, `useAscendKit()`, `useAuthModal()`, `useAnalytics()` — all work - ``, ``, ``, `` — all work - ``, ``, `` — all work What does **not** work outside Next.js: - `createAscendKitAuthRuntime()` and `createAuthRouteHandlers()` — these require Next.js for server-side session management and OAuth callbacks - Server-side analytics (`Analytics` class) — requires a Node.js server, which you can use with any framework that has a server For non-Next.js React apps, you need to proxy auth requests to a separate backend that handles the AscendKit auth runtime, or use AscendKit's hosted auth endpoints directly. --- # Auth Integration # Auth Integration AscendKit customers should integrate auth through the SDK, CLI, and portal workflows. The backend `/api/auth/*` endpoints are implementation details that back those surfaces. They are useful for internal tooling, debugging, and generated internal references, but they are not the primary customer contract. ## Customer-facing surfaces ### Next.js SDK Use the JavaScript SDK for sign-in UI, session handling, social login, and route protection. - Runtime setup: [Next.js Integration](/docs/integration) - Components: ``, ``, ``, `` - Provider config: fetched automatically from AscendKit at runtime ### Python SDK Use the Python package when your backend needs to verify access tokens or validate webhook signatures. - Token verification: [Python SDK](/docs/python#token-verification) - Webhooks: [Python SDK](/docs/python#webhook-verification) ### CLI Use the CLI to authenticate as a developer, link environments, and configure auth behavior without hand-editing config files. AI coding agents can use the CLI directly via shell commands. - Setup and login: [CLI](/docs/cli) - Auth configuration: [CLI Auth](/docs/cli/auth) - Environment linking: [CLI Environments](/docs/cli/environments) ## What customers should rely on The stable customer contract is: - SDK constructors, handlers, and components - CLI commands and their output - The portal UI - The docs pages describing those flows Customers should not build direct integrations against raw backend auth endpoints unless AscendKit explicitly documents that route as externally supported. ## Why keep OpenAPI at all Even if customers do not call backend auth endpoints directly, clean OpenAPI is still useful internally: - It gives the AscendKit team a precise contract for the SDK and CLI backing APIs. - It reduces ambiguity when agents inspect backend capabilities during support or debugging. - It makes breaking changes visible in diffs and tests. - It supports internal tooling such as typed clients, validation, and smoke checks. In other words, OpenAPI is an internal source of truth. `llms.txt` and the docs are the customer-facing explanation layer. ## How auth should be documented For customer-facing docs and `llms.txt`, the auth story should answer these questions: - How do I add auth to my app with the SDK? - How do I configure providers and auth features from the CLI? - How do I verify tokens in my backend? - What behavior is automatic versus configurable? That is more useful to customers than a raw endpoint catalog. ## Recommended split Use this split going forward: - Customer docs and `llms.txt`: SDK, CLI, and backend verification examples - Internal docs and OpenAPI: backing auth endpoints, security headers, schemas, and operational details This keeps the developer experience aligned with how customers actually adopt AscendKit. --- # Python SDK # Python SDK The `ascendkit` Python package provides access token verification and webhook signature validation. It works with any Python framework — FastAPI, Flask, Django, Starlette, or plain scripts. ## Install ```bash pip install ascendkit ``` Requires Python 3.10+. Dependencies: `httpx`, `pydantic`, `pyjwt[crypto]`. ## Access token verification Verify RS256 access tokens issued by AscendKit using JWKS. Keys are cached in memory for 1 hour. ```python from ascendkit import AccessTokenVerifier, AuthError verifier = AccessTokenVerifier() # Sync claims = verifier.verify(token) print(claims["sub"]) # usr_... print(claims["email"]) # Async claims = await verifier.verify_async(token) ``` The constructor reads `ASCENDKIT_ENV_KEY` from your environment. If the variable is missing, it raises an error at initialization so you catch configuration issues immediately. Raises `AuthError` with appropriate status codes: - `401` — expired or invalid token - `503` — JWKS endpoint unreachable ## Framework integration ```python from fastapi import FastAPI, Depends, Header, HTTPException from ascendkit import AccessTokenVerifier, AuthError app = FastAPI() verifier = AccessTokenVerifier() async def get_current_user(authorization: str = Header()) -> dict: token = authorization.removeprefix("Bearer ").strip() try: return await verifier.verify_async(token) except AuthError as e: raise HTTPException(status_code=e.status_code, detail=str(e)) @app.get("/api/profile") async def profile(user: dict = Depends(get_current_user)): return {"email": user["email"], "id": user["sub"]} ``` ```python from flask import Flask, request from ascendkit import AccessTokenVerifier app = Flask(__name__) verifier = AccessTokenVerifier() @app.route("/api/profile") def profile(): token = request.headers.get("Authorization", "").removeprefix("Bearer ").strip() claims = verifier.verify(token) # sync return {"email": claims["email"]} ``` ```python # middleware.py from ascendkit import AccessTokenVerifier, AuthError from django.http import JsonResponse verifier = AccessTokenVerifier() class AscendKitAuthMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): auth_header = request.META.get("HTTP_AUTHORIZATION", "") if auth_header.startswith("Bearer "): token = auth_header.removeprefix("Bearer ").strip() try: request.ascendkit_user = verifier.verify(token) except AuthError as e: return JsonResponse({"error": str(e)}, status=e.status_code) else: request.ascendkit_user = None return self.get_response(request) # views.py from django.http import JsonResponse def profile(request): if not request.ascendkit_user: return JsonResponse({"error": "Not authenticated"}, status=401) return JsonResponse({"email": request.ascendkit_user["email"]}) ``` ## Webhook verification Verify webhook signatures using HMAC-SHA256. Includes timestamp validation (5-minute tolerance) to prevent replay attacks. ```python from fastapi import Request, Response from ascendkit import verify_webhook_signature import os WEBHOOK_SECRET = os.environ["ASCENDKIT_WEBHOOK_SECRET"] @app.post("/webhooks/ascendkit") async def handle_webhook(request: Request) -> Response: body = await request.body() signature = request.headers.get("x-ascendkit-signature", "") if not verify_webhook_signature( secret=WEBHOOK_SECRET, signature_header=signature, payload=body.decode(), ): return Response(status_code=401, content="Invalid signature") event = await request.json() # Handle event by type: user.created, user.approved, etc. return Response(status_code=200, content="OK") ``` ```python from flask import request from ascendkit import verify_webhook_signature import os WEBHOOK_SECRET = os.environ["ASCENDKIT_WEBHOOK_SECRET"] @app.route("/webhooks/ascendkit", methods=["POST"]) def webhook(): if not verify_webhook_signature( secret=WEBHOOK_SECRET, signature_header=request.headers.get("x-ascendkit-signature", ""), payload=request.get_data(as_text=True), ): return "Invalid signature", 401 event = request.get_json() return "OK", 200 ``` ## Server-side analytics Track events from your backend with trusted identity (secret key auth): ```python from ascendkit import Analytics analytics = Analytics() analytics.track("usr_456", "checkout.completed", {"total": 99.99}) ``` The constructor reads `ASCENDKIT_SECRET_KEY` from your environment. If missing, it raises an error at initialization. Events batch in memory and flush every 30 seconds or when the batch fills (10 events). Call `analytics.shutdown()` for graceful cleanup. --- # Analytics # Analytics AscendKit Analytics captures the full user journey — from the first anonymous page visit through signup, onboarding, and beyond. No configuration required: install the SDK and analytics starts automatically. ## How it works ### Auto-collection (zero config) When the AscendKit SDK is installed, it automatically collects: - **Pageviews** — every page visit, including SPA route changes - **Device info** — browser, OS, device type - **Geography** — country, region, city (derived from IP — raw IP is never stored) - **Referrer & UTM** — where the visitor came from, campaign parameters - **Session tracking** — pageviews grouped into visits (30-minute inactivity timeout) This happens for ALL visitors — including anonymous ones who haven't signed up yet. ### Anonymous → Identified stitching Every visitor gets a `vis_` ID via a first-party cookie. When they sign up or log in, AscendKit automatically links their pre-signup browsing history to their user record: 1. Visitor browses your site (tracked under `vis_` ID) 2. Visitor signs up → AscendKit creates `usr_` record 3. All pre-signup pageviews and sessions are retroactively linked 4. First-touch attribution (source, campaign, landing page) is promoted to the user record This happens automatically — no developer action required. ### Privacy by default - IP addresses are used for geo resolution, then immediately discarded — never stored - First-party cookie only — no fingerprinting, no cross-device tracking - Anonymous visitor data auto-purges after 90 days if never linked to a user - No third-party scripts or data sharing ## Client-side (React) Track custom events from the browser. Works for both anonymous visitors and authenticated users. Browser context (userAgent, locale, screen size, referrer, URL) is captured automatically. The same `track()` call works in two modes — the SDK picks the right one based on auth state: ### Visitor tracking (pre-signup) Before the user signs up or logs in, events track against an anonymous `vis_` ID. Use these for lightweight surface interactions — the kind of things you'd see in web analytics: ```tsx import { useAnalytics } from "@ascendkit/nextjs"; // Landing page — visitor isn't signed in yet function LandingPage() { const { track } = useAnalytics(); return ( <> ); } // Docs page — still anonymous function DocsSearch() { const { track } = useAnalytics(); function onSearch(query: string) { track("docs_searched", { query }); } return ; } ``` These events are stored with the visitor's `vis_` ID. When the visitor signs up, they retroactively stitch to the `usr_` record — so you can later see "this user searched for 'auth setup' in docs before signing up." Pre-auth events feed the **analytics dashboard**, **conversion paths**, and **page flow** views in the portal. ### Business event tracking (post-signup) After the user is authenticated, events track against their `usr_` ID. Use these for meaningful application milestones — the kind of things that trigger automations: ```tsx // User is signed in — usr_ ID attached automatically function CheckoutButton() { const { track, flush } = useAnalytics(); return ( ); } function OnboardingStep({ step }: { step: number }) { const { track } = useAnalytics(); useEffect(() => { track("onboarding.step_completed", { step, stepName: "invite_team" }); }, []); return ; } ``` Post-auth events feed **journey transitions**, **engagement triggers**, **webhook dispatch**, and **funnel analysis** — in addition to the analytics dashboard. ### You don't choose a mode The developer writes the same `track()` call regardless. The SDK handles routing: | | Before signup | After signup | |---|---|---| | Identity attached | `vis_` (cookie) | `usr_` (session) | | API endpoint | `/v1/analytics/collect` | `/v1/events` | | Powers | Analytics dashboard, conversion paths | + Journeys, engagement, webhooks | Events batch in memory and flush every 30 seconds, when the batch fills (10 events), or on page hide. `flush()` is asynchronous and returns a `Promise`, so `await flush()` when you need delivery to complete before continuing. ### Event naming The SDK automatically prepends `app.` to all tracked events. So `track("checkout.completed")` fires as `app.checkout.completed` in the AscendKit system. This prefix distinguishes your application events from built-in events (like `user.created`). ## Server-side (Node.js) Track events from your backend with trusted identity (secret key auth). Useful for events that happen outside the browser — payment processing, background jobs, admin actions. ```ts import { Analytics } from "@ascendkit/nextjs/server"; const analytics = new Analytics(); analytics.track("usr_456", "checkout.completed", { total: 99.99 }); ``` The constructor reads `ASCENDKIT_SECRET_KEY` and `ASCENDKIT_ENV_KEY` from your environment. If either is missing, it throws at initialization so you catch configuration issues immediately. Events batch in memory and flush every 30 seconds, when the batch fills (10 events), or on `shutdown()`. Call `analytics.shutdown()` for graceful cleanup (e.g. in `process.on("beforeExit")`). ## Server-side (Python) ```python from ascendkit import Analytics analytics = Analytics() analytics.track("usr_456", "checkout.completed", {"total": 99.99}) ``` The constructor reads `ASCENDKIT_SECRET_KEY` from your environment. See [Python SDK](/docs/python) for full setup details. ### Passing visitor context (Python) If you have access to the visitor cookie from the client request, pass it to server-side tracking for identity linking: ```python analytics.track("usr_456", "checkout.completed", {"total": 99.99}, visitor_id="vis_abc123") ``` ## What's collected automatically vs. custom events | Data | How it's collected | |------|-------------------| | Pageviews, referrer, UTM | Automatic — no code needed | | Device, browser, OS, geo | Automatic — derived from request | | Sessions | Automatic — 30-min inactivity timeout | | Identity stitching | Automatic — on signup/login | | Custom events (button clicks, etc.) | `.track()` call | ## How analytics connects to journeys Events tracked via `track()` are available as journey transition triggers. For example: 1. You call `track("completed_onboarding")` in your app 2. AscendKit fires the event `app.completed_onboarding` 3. Any journey transition with `--on app.completed_onboarding` advances the user This means a single `track()` call simultaneously records an analytics event and drives journey automation. See [User Journeys — Application events](/docs/cli/journeys#application-events-app) for details. ## How analytics powers other services Analytics data feeds into the rest of the AscendKit platform: - **Journeys**: trigger automations based on acquisition source or pageview patterns - **Surveys**: target surveys to users from specific campaigns - **User records**: attribution data visible on every user profile ## Built-in events These events are tracked automatically by AscendKit — you do not need to call `track()` for them: | Event | Description | |-------|-------------| | `user.created` | User signs up (any method) | | `user.login` | User logs in | | `user.signout` | User signs out | | `user.waitlisted` | User placed on waitlist | | `user.waitlist_approved` | User approved from waitlist | --- # CLI Setup # CLI Setup The CLI is used to initialize your project and configure all AscendKit services. Once your environment is linked, the remaining sections of this documentation cover individual services you can configure — authentication, email, templates, surveys, journeys, webhooks, and analytics. ## Discover commands quickly Use these help modes in terminal: ```bash ascendkit --help ascendkit help survey ascendkit survey --help ascendkit survey ``` Notes: - `ascendkit --help` and `ascendkit help` both show top-level help. - Section help works with either `ascendkit help
` or `ascendkit
--help`. ## Initialize In your app's root directory: ```bash ascendkit init ``` Opens a browser window for authentication. Your credentials are saved in `.ascendkit/auth.json` in the current directory. The CLI automatically adds this directory to `.gitignore`. After login, the CLI discovers `.env` and `.env.local` files in your project and offers to seed them with the AscendKit managed environment block. That block includes your API URL, environment keys, and an `APP_URL=` placeholder for your app's public hosted URL. The CLI walks up from the current working directory until it finds an existing `.ascendkit` folder, so you only need to initialize once per project tree. To check your current session: ```bash ascendkit status ``` ## Set environment Link your directory to an AscendKit environment. You can find your public key in the [AscendKit portal](/) under your project's environment settings, or use the CLI to browse your projects: ```bash # List your projects and their environments ascendkit project list ascendkit env list --project prj_your_project_id ``` Then link to the environment: ```bash ascendkit set-env pk_dev_your_public_key ``` This fills in the public key, secret key, and API URL in your `.env`/`.env.local` files, and preserves an `APP_URL` slot for auth callback configuration. All subsequent CLI commands in this directory are scoped to that project and environment. It also writes `.ascendkit/env.json` so all subsequent CLI commands are scoped to that environment. To switch environments later, find the public key with `env list` and run: ```bash ascendkit set-env ``` ## Verify setup ```bash ascendkit verify ``` Checks connectivity to all services (Auth, Email, Content, Surveys, Journeys) and reports their status. ## Log out ```bash ascendkit logout ``` Clears the local `.ascendkit/auth.json` credentials. ## Environment variables After `ascendkit set-env` completes, the CLI populates your `.env`/`.env.local` files with the keys your SDK needs. See [Installation — Environment variables](/docs/install#environment-variables) for the full reference table. The SDK reads these environment variables automatically at runtime, so you don't need to pass them as constructor arguments or component props. The [Next.js Integration](/docs/integration) page shows both patterns if you need explicit configuration. We recommend setting `APP_URL` to the origin where your app is running — `http://localhost:3000` for local dev, or `https://app.yourdomain.com` for production. The SDK can infer the origin from incoming requests when `APP_URL` is blank, but setting it explicitly avoids "Invalid origin" errors with social login callbacks. ## Next steps Once your environment is linked, configure the services your app needs: - [Projects](/docs/cli/projects) — create projects and inspect available environments - [Environments](/docs/cli/environments) — switch environments and manage environment variables - [Auth Configuration](/docs/cli/auth) — sign-in methods, OAuth providers, email verification, waitlist - [Email Identity](/docs/cli/email) — sender address and domain verification - [Email Templates](/docs/cli/templates) — customize transactional email content - [Surveys](/docs/cli/surveys) — NPS, CSAT, and custom surveys with distribution tracking - [User Journeys](/docs/cli/journeys) — automated lifecycle flows triggered by user events - [Webhooks](/docs/cli/webhooks) — real-time event notifications to your server - [Campaigns](/docs/cli/campaigns) — targeted email campaigns with scheduling and analytics - [User Import & Migration](/docs/cli/import) — import users from Clerk and set up migration journeys - [Analytics](/docs/analytics) — track user behavior from client and server --- # Projects # Projects Use the project commands to create projects, inspect available environments, and select the active environment for the CLI. ## List projects ```bash ascendkit project list ``` ## Create a project ```bash ascendkit project create \ --name "Acme App" \ --description "Customer onboarding app" \ --services auth,content,surveys ``` ## Show a project ```bash ascendkit project show prj_abc123 ``` Every new project gets a development environment automatically. Use `ascendkit env list` to inspect environments and choose which one should become active in `.ascendkit/env.json`. ## Work with environments ```bash ascendkit env list --project prj_abc123 ascendkit set-env ascendkit environment show ascendkit environment update --name "Preview" ascendkit environment promote --target production ``` Use [the environment variable commands](/docs/cli/environments) to manage runtime key-value settings for the active environment. --- # Environments # Environments Use the environment CLI to inspect and update the active environment after you have selected it with `set-env`. The active environment is stored in `.ascendkit/env.json`. The CLI walks up from the current working directory until it finds the nearest `.ascendkit` folder. ## Select an environment ```bash ascendkit env list --project prj_abc123 ascendkit set-env ``` `set-env` updates the local CLI context. Variable commands below apply to the currently selected environment. If local `.env`, `.env.local`, or `.env.example` files are present in the project tree, environment selection also syncs the AscendKit-managed keys into those files. ## Show the active environment ```bash ascendkit environment show ``` ## Environment Variables Environment runtime settings are stored as key-value pairs on the environment and appear on the environment settings page in the portal. ### List values ```bash ascendkit vars list ``` ### Set a value ```bash ascendkit vars set API_BASE_URL https://api.example.com ascendkit vars set SUPPORT_EMAIL support@example.com ``` ### Remove a value ```bash ascendkit vars remove SUPPORT_EMAIL ``` ## Update environment metadata ```bash ascendkit environment update \ --name "Preview" \ --description "Customer QA environment" ``` ## Promote an environment ```bash ascendkit environment promote --target production ``` ## Variable key rules - Keys must start with a letter or underscore. - Keys may contain letters, digits, underscores, and hyphens. - Values are stored as strings. --- # Auth Configuration # Auth Configuration Control how users sign in to your application. AscendKit supports multiple authentication methods that you can enable and combine without changing your app code — credentials (email/password), magic-link passwordless auth, and social OAuth providers. **Supported providers**: `credentials` (email/password), `magic-link` (passwordless email link), `google`, `github`, `apple`, `discord`, `microsoft` **Configurable features**: - **Email verification** — require users to verify their email before signing in - **Password reset** — let users reset their password via email - **Waitlist** — hold new signups for manual approval before granting access - **Session duration** — how long a user stays signed in - **Domain allowlist** — restrict signups to specific email domains (e.g., `company.com`) - **Block personal domains** — block signups from consumer email providers (Gmail, Yahoo, etc.) Social OAuth providers work immediately in dev and beta environments using AscendKit's managed credentials. In production, or when you want your own branding on the consent screen, you can configure your own OAuth client credentials. ## View current settings ```bash ascendkit auth show ``` Shows enabled providers, feature flags, and session duration. ## Update settings ```bash ascendkit auth update --providers credentials,google,github --email-verification true --session-duration 30d ``` | Flag | Description | |------|-------------| | `--providers` | Comma-separated list: `credentials`, `magic-link`, `google`, `github`, `apple`, `discord`, `microsoft` | | `--email-verification` | Require email verification on signup | | `--password-reset` | Enable password reset flow | | `--waitlist` | Enable waitlist mode | | `--session-duration` | Session lifetime (e.g., `7d`, `24h`, `30d`) | | `--allowed-domains` | Comma-separated list of allowed email domains for signup | | `--block-personal-domains` | `true`/`false` — block consumer email providers (Gmail, Yahoo, etc.) | ## Manage providers Show enabled providers: ```bash ascendkit auth provider list ``` Set the provider list: ```bash ascendkit auth provider set credentials,google,github ``` Use `magic-link` in the provider list when you want passwordless email-link sign-in: ```bash ascendkit auth provider set magic-link,google ``` ## Configure OAuth credentials When you enable a social provider (Google, GitHub, etc.), it works immediately using AscendKit's managed credentials in dev and beta environments. You can optionally configure your own OAuth credentials when you want your own branding on the consent screen. ### CLI-based setup ```bash # Recommended: pass secret via stdin (avoid shell history) echo "$GOOGLE_CLIENT_SECRET" | ascendkit auth oauth set google \ --client-id "$GOOGLE_CLIENT_ID" \ --client-secret-stdin ``` ```bash # Alternate: direct flag (less secure; may be stored in shell history) ascendkit auth oauth set google \ --client-id "$GOOGLE_CLIENT_ID" \ --client-secret "$GOOGLE_CLIENT_SECRET" ``` | Flag | Description | |------|-------------| | `--client-id` | OAuth client ID from the provider's developer console | | `--client-secret` | OAuth client secret (may appear in shell history) | | `--client-secret-stdin` | Read client secret from stdin (recommended) | When using your own credentials, set `APP_URL` for the environment first. AscendKit derives the redirect URI you need to register in the provider's developer console: ``` https:///api/auth/callback/{provider} ``` ## Domain restrictions Restrict which email domains can sign up. Useful for employee-only apps or B2B products that need business email addresses. ### Allow specific domains only ```bash ascendkit auth update --allowed-domains company.com,partner.org ``` Only users with email addresses from these domains can sign up. Existing users are unaffected. ### Block personal email providers ```bash ascendkit auth update --block-personal-domains true ``` Blocks signups from consumer email providers (Gmail, Yahoo, Hotmail, Outlook, etc.). AscendKit maintains the blocklist. ### Combine both When both are set, the allowlist takes precedence — personal domain blocking has no additional effect. ### Clear restrictions ```bash ascendkit auth update --allowed-domains "" ascendkit auth update --block-personal-domains false ``` Domain restrictions apply to all signup methods including OAuth. The email returned by the OAuth provider is checked against the same rules. ## Manage users ```bash ascendkit auth user list ascendkit auth user list --unsubscribed ascendkit auth user remove usr_abc123 ascendkit auth user reactivate usr_abc123 ``` `ascendkit auth user list` shows a derived `Status` column for each user returned by the command: - `active` - `deactivated` - `waitlisted` - `rejected` - `unsubscribed` Use `--unsubscribed` to list only users who opted out of campaigns. --- # Email Identity # Email Identity AscendKit email configuration has two layers: - A verified sending domain - One or more verified sender identities on that domain The CLI uses the same backend APIs as the portal. Domain setup, identity management, status refresh, default sender selection, and test sends all map to the `/api/email/settings` backend routes. ## View current email settings ```bash ascendkit email-identity settings ascendkit email-identity settings --json ``` By default this prints a compact summary of the configured domain, DNS provider, default sender, and sender identities. Use `--json` to print the full backend payload. ## Set up a custom domain ```bash ascendkit email-identity setup-domain yourapp.com ascendkit email-identity status ``` - `setup-domain` creates the SES domain identity and returns the DNS records you need to add. - `status` checks the SES domain status and public DNS records together. To remove the custom domain and reset the environment back to the AscendKit sender: ```bash ascendkit email-identity remove-domain ``` ## Manage sender identities List identities and refresh their SES verification state: ```bash ascendkit email-identity list ``` Add a new identity: ```bash ascendkit email-identity add dev-noreply@yourapp.com --display-name "Your App" ``` Update the display name for an existing identity: ```bash ascendkit email-identity update dev-noreply@yourapp.com --display-name "New Name" ``` Request SES to resend inbox verification: ```bash ascendkit email-identity resend dev-noreply@yourapp.com ``` Set the default sender identity: ```bash ascendkit email-identity set-default dev-noreply@yourapp.com --display-name "Your App" ``` Remove an identity: ```bash ascendkit email-identity remove dev-noreply@yourapp.com ``` Send a test email from a verified identity: ```bash ascendkit email-identity test dev-noreply@yourapp.com --to you@example.com ``` ## DNS provider shortcuts Detect the DNS provider for the configured domain and print the provider console URL: ```bash ascendkit email-identity open-dns ``` You can also detect a provider for a specific domain: ```bash ascendkit email-identity open-dns --domain yourapp.com ``` ## Email suppressions `email-suppression list` shows every address currently excluded from campaign delivery in the active environment. That includes SES-style suppressions (`bounce`, `complaint`) and user opt-outs (`unsubscribe`). List excluded addresses for the current environment: ```bash ascendkit email-suppression list ascendkit email-suppression list --reason unsubscribe ascendkit email-suppression list --skip 0 --limit 50 ``` | Flag | Description | |------|-------------| | `--reason` | Filter to `bounce`, `complaint`, or `unsubscribe` | | `--skip` | Pagination offset (default `0`) | | `--limit` | Number of rows to return (default `100`, max `500`) | Remove a bounce or complaint suppression entry manually: ```bash ascendkit email-suppression remove user@example.com ``` `remove` only applies to bounce and complaint suppressions. Unsubscribed users remain excluded until they opt back in through application logic or a future resubscribe flow. Campaigns automatically exclude all of these addresses from delivery. --- # Email Templates # Email Templates Customize the content of transactional emails that AscendKit sends to your users. Every email AscendKit delivers — verification, password reset, magic link, welcome, and journey emails — is rendered from a template. You can modify the default templates or create new ones for use in journeys. Templates support HTML and plain-text bodies with `{{variable}}` placeholders that are filled at send time (e.g., `{{userName}}`, `{{appName}}`, `{{verificationLink}}`). Every update creates a new immutable version, so you can review and roll back changes. ## List templates ```bash ascendkit template list ascendkit template list --query "welcome" ascendkit template list --system true ascendkit template list --query "receipt" ``` ## Create a template ```bash ascendkit template create \ --name "Welcome Email" \ --slug welcome-email \ --subject "Welcome to {{appName}}" \ --body-html "

Welcome, {{userName}}!

" \ --body-text "Welcome, {{userName}}!" \ --category marketing ``` ## View a template ```bash ascendkit template show tpl_abc123 ``` ## Update a template ```bash ascendkit template update tpl_abc123 \ --subject "New subject line" \ --change-note "Updated subject for A/B test" # --html and --text are accepted as aliases for --body-html and --body-text ascendkit template update tpl_abc123 \ --html "

Hello, {{userName}}!

" \ --text "Hello, {{userName}}!" ascendkit template update tpl_abc123 \ --category transactional \ --change-note "This journey email is operational, not marketing" ``` Each update creates a new immutable version. Previous versions are preserved. > **Tip**: Run `ascendkit template update --help` to see all available flags before editing. ## Template categories Templates support a `category` field: - `marketing` injects the unsubscribe footer and `List-Unsubscribe` headers for journey sends. - `transactional` skips unsubscribe injection for operational emails like password resets, magic links, OTPs, and receipts. The CLI defaults new templates to `marketing`. Set `--category transactional` when the email should never present an unsubscribe action. ## View version history ```bash ascendkit template version list tpl_abc123 ascendkit template version show tpl_abc123 2 ``` ## Delete a template ```bash ascendkit template remove tpl_abc123 ``` ## Personalizing templates for your brand When customizing templates for your app, follow this workflow: 1. **Set environment variables first** — use `ascendkit vars set dashboardUrl "https://yourapp.com/dashboard"` so templates can reference `{{dashboardUrl}}` dynamically. Note: `{{appName}}` is automatically resolved from your environment name 2. **Use `{{variable}}` placeholders** — don't hardcode your app name or URLs in template HTML. If you rename your app or change domains, you only need to update the environment variable 3. **Review linked surveys** — if you update survey invite templates, also review the survey questions themselves with `ascendkit survey show` to ensure they match your branding 4. **Verify after updating** — run `ascendkit template show tpl_xxx` after updates to confirm the HTML and variables rendered correctly 5. **Check for warnings** — the CLI will warn you if a template update removes variables that were present in the previous version --- # Surveys # Surveys Collect feedback from your users with in-app surveys. AscendKit supports three survey types: **NPS** (Net Promoter Score) for loyalty measurement, **CSAT** (Customer Satisfaction) for experience ratings, and **custom** surveys for any question format using SurveyJS. Surveys integrate with the rest of AscendKit — distribute them through journeys, trigger webhooks on submission, and track completion analytics with per-user funnels (sent, opened, submitted). > **Note**: The CLI handles survey-level operations (create, distribute, view analytics). Question-level editing (add, edit, reorder questions) is available through the [portal](/) UI. ## Create a survey ```bash ascendkit survey create --name "Q1 NPS Survey" --type nps ascendkit survey create --name "Onboarding CSAT" --type csat ascendkit survey create --name "Feature Feedback" --type custom ``` NPS and CSAT surveys come with standard questions pre-configured. Custom surveys start empty. ## List surveys ```bash ascendkit survey list ``` Shows all surveys with their status: needs questions, draft, active, or collecting responses. ## Export/import definition JSON Export current definition: ```bash ascendkit survey definition export srv_abc123 --out survey-definition.json ``` Edit `survey-definition.json`, then import it: ```bash ascendkit survey definition import srv_abc123 --in survey-definition.json ``` `definition import` updates only the `definition` field. It does not mutate `slug`, `name`, or `status`. ## Activate and distribute ```bash ascendkit survey update srv_abc123 --status active ascendkit survey distribute srv_abc123 --users usr_001,usr_002,usr_003 ``` Distribution creates personalized links per user for tracking (sent, opened, submitted). ## View results ```bash ascendkit survey invitation list srv_abc123 ascendkit survey analytics srv_abc123 ``` Analytics shows the funnel (sent -> opened -> submitted), conversion rates, and NPS score for NPS surveys. ## Delete a survey ```bash ascendkit survey remove srv_abc123 ``` Deletes the survey and all associated invitations and responses. ## Question operations ```bash ascendkit survey question list srv_abc123 ascendkit survey question add srv_abc123 --type text --title "What should we improve?" ascendkit survey question update srv_abc123 feedback --title "Anything we should improve?" ascendkit survey question remove srv_abc123 feedback ascendkit survey question reorder srv_abc123 --order nps_score,feedback ``` > **Note**: Question `--type` cannot be changed on an existing question. To change the type, remove the question and re-add it. The `update` command will return an error if `--type` is passed. --- # User Journeys # User Journeys Automate actions based on user behavior over time. A journey is a graph of nodes (actions) connected by transitions (triggers). When a user fires the entry event, they enter the journey and progress through nodes automatically. **Use cases**: send a welcome email after signup, deliver a survey after onboarding, nudge inactive users after 3 days, tag users who complete a milestone, or build multi-step onboarding sequences with timed follow-ups. **How it works**: Each node performs an action (send an email, deliver a survey, tag a user). Transitions move users between nodes when an event occurs (`--on user.login`) or after a delay (`--after 2d`). Journeys are built incrementally — create a scaffold, then add nodes and transitions one at a time. ## Create a journey Creates a minimal journey with an auto-created entry node (defaults to `none` action): ```bash ascendkit journey create \ --name "Onboarding Flow" \ --entry-event user.created \ --entry-node welcome_email ``` Then build it up with `add-node` and `add-transition` commands. ## Inspect a journey ### View full journey ```bash ascendkit journey show jrn_abc123 ``` Shows the full definition (nodes, transitions) and stats (enrolled, active, completed). Transition IDs are shown inline so you can reference them directly in `transition update` or `transition remove`: ``` Transitions: welcome_email → nudge_1 [timer (2d)] (welcome_email-to-nudge_1) welcome_email → done [on user.login] (welcome_email-to-done) ``` > **Note**: Topology warnings are suppressed when the journey is paused, since mid-construction gaps are expected. Run `journey show` after activating to confirm the graph is valid. ### List nodes ```bash ascendkit journey node list jrn_abc123 ``` Shows all nodes with their actions, terminal status, and transition counts. ### List transitions ```bash ascendkit journey transition list jrn_abc123 ascendkit journey transition list jrn_abc123 --from welcome_email ascendkit journey transition list jrn_abc123 --to done ``` Shows transitions in human-readable format ("on user.login -> done", "after 2d -> nudge_1"). Filter by source or destination node. ## Add nodes ```bash ascendkit journey node add jrn_abc123 \ --name welcome_email \ --action '{"type": "send_email", "templateSlug": "welcome-email"}' ascendkit journey node add jrn_abc123 \ --name nudge_1 \ --action '{"type": "send_email", "templateSlug": "login-nudge"}' # With a custom sender identity ascendkit journey node add jrn_abc123 \ --name welcome_email \ --action '{"type": "send_email", "templateSlug": "welcome-email"}' \ --email-id support@myapp.com ascendkit journey node add jrn_abc123 \ --name done \ --terminal ``` Use `--quiet` to suppress topology warnings when building incrementally (e.g. wiring transitions next): ```bash ascendkit journey node add jrn_abc123 --name post_session --action '...' --quiet ``` ## Edit nodes Update a node's action or terminal status: ```bash # Add a survey to an existing email node ascendkit journey node update jrn_abc123 \ welcome_email \ --action '{"type": "send_email", "templateSlug": "welcome-email", "surveySlug": "onboarding-nps"}' # Remove the survey (keep the email) ascendkit journey node update jrn_abc123 \ welcome_email \ --action '{"type": "send_email", "templateSlug": "welcome-email"}' # Make a node terminal ascendkit journey node update jrn_abc123 \ done \ --terminal # Change just the sender identity (keeps existing action) ascendkit journey node update jrn_abc123 welcome_email \ --email-id noreply@myapp.com ``` ## Remove nodes Removes the node and auto-cascades all transitions to/from it: ```bash ascendkit journey node remove jrn_abc123 nudge_1 ``` ## Add transitions Use `--on` for event triggers or `--after` for timer delays: ```bash # Event trigger: "on user.login, go to done" ascendkit journey transition add jrn_abc123 \ --from welcome_email \ --to done \ --on user.login # Timer trigger: "after 2 days, go to nudge_1" ascendkit journey transition add jrn_abc123 \ --from welcome_email \ --to nudge_1 \ --after 2d # With explicit name (otherwise auto-generated as "welcome_email-to-done") ascendkit journey transition add jrn_abc123 \ --from welcome_email \ --to done \ --on user.login \ --name login-success # Suppress topology warnings while wiring mid-construction ascendkit journey transition add jrn_abc123 \ --from welcome_email \ --to nudge_1 \ --after 2d \ --quiet ``` The `--after` flag accepts durations like `30m`, `12h`, `2d`, `1w`. A bare number defaults to minutes. ## Edit transitions Target a transition by its name: ```bash ascendkit journey transition update jrn_abc123 \ welcome_email-to-nudge_1 \ --after 3d ascendkit journey transition update jrn_abc123 \ login-success \ --on user.completed_onboarding ``` ## Remove transitions ```bash ascendkit journey transition remove jrn_abc123 welcome_email-to-nudge_1 ``` ## List journeys ```bash ascendkit journey list ascendkit journey list --status active ``` ## Update journey metadata Full-graph update for power users. Only provided fields are changed: ```bash ascendkit journey update jrn_abc123 --name "Updated Onboarding" ascendkit journey update jrn_abc123 --nodes '{...}' --transitions '[...]' ``` When you provide `--nodes`, any `send_email` node can also include `fromIdentityEmail` directly in the JSON: ```bash ascendkit journey update jrn_abc123 \ --nodes '{ "welcome_email": { "action": { "type": "send_email", "templateSlug": "welcome-email", "fromIdentityEmail": "support@myapp.com" }, "terminal": false } }' ``` ## Journey lifecycle ```bash ascendkit journey activate jrn_abc123 # Start enrolling users ascendkit journey pause jrn_abc123 # Pause — actions queue, transitions continue ascendkit journey archive jrn_abc123 # Permanent — exits all users, no undo ``` When pausing a journey with active users, the CLI will ask for confirmation before proceeding. Use `--yes` to skip: ```bash ascendkit journey pause jrn_abc123 --yes ``` Once active, users are automatically enrolled when the entry event fires. ## Entry conditions Filter which users enter the journey based on event properties. When the entry event fires, AscendKit checks the event's properties against the conditions — all key-value pairs must match for the user to enroll. ```bash ascendkit journey create \ --name "Credentials Onboarding" \ --entry-event user.created \ --entry-conditions '{"provider": "credentials"}' \ --entry-node welcome_email ``` For custom app events, the properties you pass to `track()` are available as conditions: ```bash # Only enroll users who enrolled in the silver tier ascendkit journey create \ --name "Silver Tier Onboarding" \ --entry-event app.package.enrolled \ --entry-conditions '{"tier": "silver"}' \ --entry-node welcome_email ``` This matches events where `track("package.enrolled", { tier: "silver" })` was called. ## Re-entry policy Control what happens if a user triggers the entry event again: ```bash ascendkit journey update jrn_abc123 --re-entry-policy skip # Ignore (default) ascendkit journey update jrn_abc123 --re-entry-policy restart # Re-enroll ``` ## Analytics ```bash ascendkit journey analytics jrn_abc123 ``` Shows user counts per node, conversion rates per transition, time-in-node metrics, and totals (enrolled, active, completed, failed). ## Delete a journey ```bash ascendkit journey remove jrn_abc123 ``` Deletes the journey and all associated user states. ## Node action types | Type | Description | Required fields | |------|-------------|-----------------| | `send_email` | Send an email template, optionally with a survey | `templateSlug`, optional `surveySlug`, `fromIdentityEmail` | | `tag_user` | Tag the user | `tagName` | | `advance_stage` | Set lifecycle stage | `stageName` | | `none` | No-op, used for wait/branching nodes | — | ## Email sender identity Use the `--email-id` flag to control which verified email identity sends emails for a node: ```bash ascendkit journey node add jrn_abc123 \ --name welcome_email \ --action '{"type": "send_email", "templateSlug": "welcome-email"}' \ --email-id support@myapp.com ``` **Resolution order** — when a `send_email` node executes, the sender is resolved as: 1. **Node identity** — the `--email-id` value set on this specific node 2. **Environment default** — the default verified identity configured in your email settings 3. **AscendKit default** — `noreply@ascendkit.dev` if no custom identity is set up **Validation** — when you set `--email-id`, the backend checks whether the identity exists and is verified in the current environment. If it's missing or unverified, a **warning** is returned but the node is still saved. This lets you configure nodes before completing domain verification. The same optional sender override is supported inside `--nodes` JSON as `fromIdentityEmail` for any `send_email` node. ## Transition triggers | Type | Description | Fields | |------|-------------|--------| | `event` | On user event (e.g., `--on user.login`) | `event` (event name) | | `timer` | After time elapses (e.g., `--after 3d`) | `delay` (e.g., `3d`, `24h`, `30m`) | ## Transition event filters Event transitions can filter on event properties so a transition only fires when specific property values match. Add an `eventFilter` to the trigger JSON: ```bash ascendkit journey transition add jrn_abc123 \ --from trial_started \ --to pro_onboarding \ --trigger '{"type": "event", "event": "app.upgraded_plan", "eventFilter": {"plan": "pro"}}' ``` This transition only fires when `track("upgraded_plan", { plan: "pro" })` is called — other plan values are ignored. All key-value pairs in `eventFilter` must match (AND logic). Values are compared with strict equality. See [How event properties flow into journeys](#how-event-properties-flow-into-journeys) for end-to-end examples with both SDKs. ## Available events ### Built-in events These are fired automatically by AscendKit: | Event | Description | |-------|-------------| | `user.created` | User signs up (any method) | | `user.login` | User logs in | | `user.signout` | User signs out | | `user.waitlisted` | User placed on waitlist | | `user.waitlist_approved` | User approved from waitlist | ### Application events (`app.*`) Your application can fire custom events via the SDK. AscendKit prepends `app.` to all tracked events, so `track("completed_onboarding")` fires as `app.completed_onboarding`. #### Tracking events from your app **JavaScript/TypeScript** (client-side React hook): ```typescript const { track } = useAnalytics(); // Simple event track("completed_onboarding"); // → fires app.completed_onboarding // Event with properties — properties are used for journey routing track("upgraded_plan", { plan: "pro", interval: "annual" }); // → fires app.upgraded_plan with properties { plan: "pro", interval: "annual" } track("package.enrolled", { packageId: "pkg_abc", tier: "silver", paymentStatus: "paid" }); // → fires app.package.enrolled with those properties ``` **Python** (server-side, secret key auth): ```python from ascendkit import Analytics analytics = Analytics() # reads ASCENDKIT_SECRET_KEY from env analytics.track("usr_456", "completed_onboarding") analytics.track("usr_456", "upgraded_plan", {"plan": "pro", "interval": "annual"}) analytics.track("usr_456", "package.enrolled", {"packageId": "pkg_abc", "tier": "silver"}) ``` #### How event properties flow into journeys Properties passed to `track()` are available at two points in the journey lifecycle: **1. Entry conditions** — filter which users enroll in a journey (see [Entry conditions](#entry-conditions)): ```bash # Only enroll users who upgraded to the pro plan ascendkit journey create \ --name "Pro Plan Onboarding" \ --entry-event app.upgraded_plan \ --entry-conditions '{"plan": "pro"}' \ --entry-node welcome_email ``` When `track("upgraded_plan", { plan: "pro" })` fires, the user enrolls. When `track("upgraded_plan", { plan: "starter" })` fires, they don't. **2. Transition event filters** — control which transitions fire based on properties (see [Transition event filters](#transition-event-filters)): ```bash # Only advance when the user upgrades to an annual plan ascendkit journey transition add jrn_abc123 \ --from trial_active \ --to annual_onboarding \ --trigger '{"type": "event", "event": "app.upgraded_plan", "eventFilter": {"interval": "annual"}}' ``` Both use strict key-value equality with AND logic — all specified pairs must match. #### Using app events as transition triggers ```bash ascendkit journey transition add jrn_abc123 \ --from welcome_email \ --to done \ --on app.completed_onboarding ``` #### Event properties and email templates Event properties are used for journey **routing** (entry conditions, transition filters) but are **not** automatically available as email template variables. Email templates use a separate variable system: | Variable | Source | Available in | |----------|--------|-------------| | `{{userName}}` | Recipient name or email prefix | Journey, campaign | | `{{email}}` | Recipient email address | Journey, campaign | | `{{appName}}` | Environment name | Journey, campaign | | `{{envName}}` | Environment name | Journey, campaign | | `{{provider}}` | Recipient's OAuth provider | Journey | | `{{surveyLink}}` | Generated survey invitation link | Journey | You can also define custom variables at the environment level or on individual journey node actions. See the [email templates](../integration.md) docs for details. ## Transition naming Transitions are identified by name for editing and removal: - **Auto-generated**: `{from}-to-{to}` (e.g., `welcome_email-to-nudge_1`) - **Duplicates**: suffixed (`welcome_email-to-done-2`) - **Custom**: provide `--name` on `add-transition` to override - **Env-stable**: names are developer-defined strings, preserved across environment promotion --- # Webhooks # Webhooks Receive real-time notifications on your server when events happen in AscendKit. Webhooks let your backend react immediately to user signups, waitlist approvals, survey submissions, and journey completions — without polling the API. **Use cases**: sync new users to your CRM, send a Slack notification when someone joins the waitlist, update billing when a user is approved, or trigger external workflows when a survey is submitted. Every webhook request is signed with HMAC-SHA256 so your server can verify it came from AscendKit. See [Python SDK — Webhook verification](/docs/python#webhook-verification) or the [Next.js Integration](/docs/integration) for implementation examples. ## Create a webhook ```bash ascendkit webhook create \ --url https://yourapp.com/webhooks/ascendkit \ --events user.created,user.waitlist_approved ``` | Flag | Description | |------|-------------| | `--url` | Your endpoint URL (must be HTTPS in production) | | `--events` | Comma-separated list of event types to subscribe to | ## List webhooks ```bash ascendkit webhook list ``` ## View a webhook ```bash ascendkit webhook get whk_abc123 ``` ## Update a webhook ```bash ascendkit webhook update whk_abc123 \ --url https://yourapp.com/webhooks/v2 \ --events user.created,user.login ``` ## Test a webhook Send a test event to verify your endpoint is working: ```bash ascendkit webhook test whk_abc123 ``` ## Delete a webhook ```bash ascendkit webhook delete whk_abc123 ``` ## Available events | Event | Description | |-------|-------------| | `user.created` | User signs up (any method) | | `user.login` | User logs in | | `user.signout` | User signs out | | `user.waitlisted` | User placed on waitlist | | `user.waitlist_approved` | User approved from waitlist | | `survey.submitted` | User submits a survey response | | `journey.user_entered` | User enters a journey | | `journey.user_completed` | User completes a journey | --- # Campaigns # Campaigns Send targeted email campaigns to segments of your users. Campaigns combine an email template with an audience filter and an optional schedule, so you can preview who will receive the email before it goes out. **Use cases**: announce a new feature to active users, send a re-engagement email to churned users, distribute a newsletter on a schedule, or run a promotional campaign targeted by user attributes. ## Create a campaign ```bash ascendkit campaign create \ --name "March Newsletter" \ --template tpl_abc123 \ --audience '{"tags":{"$in":["premium"]}}' \ --from noreply@yourdomain.com \ --scheduled-at 2026-03-15T10:00:00Z ``` | Flag | Description | |------|-------------| | `--name` | Campaign name (required) | | `--template` | Template ID to use for the email body (required) | | `--audience` | JSON filter for targeting users (required) | | `--from` | Verified email identity to send from (optional; defaults to environment default) | | `--scheduled-at` | ISO 8601 datetime to send (optional; omit to create as draft) | ## List campaigns ```bash ascendkit campaign list ascendkit campaign list --status scheduled ``` Filter by status: `draft`, `scheduled`, `sending`, `sent`, `failed`, `cancelled`. ## View a campaign ```bash ascendkit campaign show cmp_abc123 ``` ## Update a campaign Update a draft, scheduled, or failed campaign before it sends: ```bash ascendkit campaign update cmp_abc123 \ --name "Updated Newsletter" \ --template tpl_def456 \ --from noreply@yourdomain.com ``` ## Preview audience See which users match the campaign's audience filter without sending: ```bash ascendkit campaign preview cmp_abc123 ``` ## Schedule a campaign Set or change the send time for an existing campaign: ```bash ascendkit campaign schedule cmp_abc123 --at 2026-03-20T14:00:00Z ``` ## Cancel a campaign Delete a draft or failed campaign, or cancel a scheduled or sending campaign: ```bash ascendkit campaign cancel cmp_abc123 ``` ## View analytics After a campaign is sent, check delivery and engagement metrics: ```bash ascendkit campaign analytics cmp_abc123 ``` The analytics output includes: - Sent, delivered, failed, suppressed, and bounced delivery counts - Opened and clicked engagement counts - Unsubscribed and complained counts - Calculated rates for open rate, click-to-open rate, bounce rate, and unsubscribe rate --- # User Import & Migration # User Import & Migration Import users from your existing auth provider into AscendKit. Currently supports [Clerk](https://clerk.com). ## Prerequisites 1. [Initialize the CLI](/docs/cli) and link your environment with `ascendkit set-env` 2. Have your Clerk secret API key ready (found in Clerk Dashboard → API Keys) ## Import workflow ### 1. Preview settings (dry run) See what auth methods are detected from your Clerk users without making changes: ```bash ascendkit import clerk preview --api-key sk_live_... --settings ``` This fetches all users from Clerk, analyzes their auth methods, and shows what providers would be enabled (credentials, Google, LinkedIn, etc.). ### 2. Preview users (dry run) Preview the full import — users and settings: ```bash ascendkit import clerk preview --api-key sk_live_... ``` By default, the import runs in **dry-run mode**. It shows how many users would be imported, how many are duplicates, and what auth settings would be applied. ### 3. Execute the import When you're satisfied with the preview: ```bash ascendkit import clerk run --api-key sk_live_... ``` This imports users, creates OAuth account links, updates auth settings, and emits lifecycle events. ### 4. Create migration journeys Set up email templates and draft journeys for notifying your users: ```bash ascendkit import migration-journey create ``` This creates: - **5 email templates**: announcement, go-live, reminder, password reset, and password reset reminder - **2 draft journeys**: - **Announcement cadence** (all users): Day 0 announcement → Day 3 go-live → Day 7 reminder - **Password reset cadence** (credential users only): Day 1 password reset → Day 4 reminder Journeys are created in **draft** status. Review and customize the templates before activating. ### 5. Review and customize ```bash ascendkit template list ascendkit journey list ascendkit journey show ``` Edit templates in the portal to match your brand. Update journey node variables (e.g., `loginUrl`, `resetUrl`) to point to your app. ### 6. Activate journeys ```bash ascendkit journey activate ``` Migration journeys automatically backfill enrollment for previously imported users when activated. You don't need to re-import or re-emit events — all imported users are enrolled at activation time. ## Command reference ### `ascendkit import clerk preview|run` ``` ascendkit import clerk preview --api-key [options] ascendkit import clerk preview --file [options] ascendkit import clerk run --api-key [options] ascendkit import clerk run --file [options] ``` | Flag | Description | |------|-------------| | `--api-key ` | Clerk secret API key (fetches users from Clerk API) | | `--file ` | Path to Clerk JSON export (alternative to API) | | `--users` | Import only users (skip auth settings) | | `--settings` | Import only auth settings (skip users) | | `--providers ` | Override SSO providers, comma-separated (e.g., `--providers linkedin`) | | `--from-identity ` | Sender email for migration journey emails | Use `preview` for dry-run mode and `run` to apply changes. By default, both users and settings are imported. Pass `--users` or `--settings` alone to run only that phase. ### `ascendkit import migration-journey create` ``` ascendkit import migration-journey create [--from-identity ] ``` Creates migration templates and journeys in draft status. Pass `--from-identity` to set the sender email on all journey email nodes. ## What gets imported ### Users | Source (Clerk) | Destination (AscendKit) | |----------------|------------------------| | `id` | `metadata.import.sourceId` | | `email_addresses[0]` | `email` | | `first_name` + `last_name` | `name` | | `image_url` | `image` | | `email_addresses[].verification.status` | `emailVerified` | | `password_enabled` | Tag: `import:needs-password-reset` | | `external_accounts` | OAuth account links | | `created_at` | `createdAt` (original signup date preserved) | | `last_sign_in_at` | `lastLoginAt` | | `public_metadata` | `metadata` | ### Auth settings Providers are detected from user data: - Users with `password_enabled: true` → enables `credentials` - Users with linked `external_accounts` → enables corresponding OAuth providers (google, linkedin, github, etc.) Use `--providers` to override which SSO providers are enabled if some are no longer active in Clerk. ### Tags Imported users are tagged for segmentation: | Tag | Meaning | |-----|---------| | `import:clerk` | Imported from Clerk | | `import:needs-password-reset` | Had a password in Clerk, needs to set a new one | | `import:social-only` | Only used OAuth login (no password) | ### Events The import emits events that power journeys and analytics: | Event | When | Payload | |-------|------|---------| | `user.imported` | Each user imported | `source`, `sourceId`, `hadPassword`, `hasOAuth` | | `user.login` | Users with prior login activity | `synthetic: true`, `originalLoginAt` | The `user.login` event is synthetic — it preserves the user's last login timestamp from Clerk so existing journeys that trigger on login events work correctly. ## Migration journey details ### Announcement cadence (all imported users) | Timing | Template | Purpose | |--------|----------|---------| | Day 0 | `migration-announcement` | "We're moving to a new platform" | | Day 3 | `migration-go-live` | "Migration is live — try logging in" | | Day 7 | `migration-reminder` | "Have you logged in yet?" | ### Password reset cadence (credential users only) | Timing | Template | Purpose | |--------|----------|---------| | Day 1 | `migration-password-reset` | "Set your new password" | | Day 4 | `migration-password-reset-reminder` | "Reminder: set your password" | Social-only users receive only the announcement cadence — their OAuth login carries over automatically. ## Importing from a file If you prefer to export from Clerk's dashboard instead of using the API: ```bash ascendkit import clerk run --file ./clerk-export.json ``` The file should be a JSON array of Clerk user objects or an object with a `users` key. --- # Analytics CLI # Analytics CLI Query visitor traffic, acquisition data, and conversion metrics from the terminal. ## Commands ### View analytics summary ```bash ascendkit analytics summary ``` Shows total visitors, pageviews, unique visitors, and signup conversion rate. ### View top pages ```bash ascendkit analytics pages [--limit 20] ``` ### View acquisition sources ```bash ascendkit analytics acquisition [--limit 20] ``` Shows top referrers and UTM campaign breakdown. ### View user attribution ```bash ascendkit analytics user ``` Shows acquisition data for a specific user: how they found your product, what they browsed before signing up.