← Back to path picker
Track 1 · Lovable

Build with Lovable.
Add privacy in five steps.

~10 minutes. One step at a time. The last step verifies your integration end-to-end.

This pattern works with any browser-based AI coding platform — Lovable, Vercel v0, Base44, Bolt, and others. The only platform-specific step is Step 3 — where you paste your appKey into your platform's secrets UI.

Already have a Lovable app? The integration is additive — your existing app, auth, and UI stay intact. Skip to Step 04 and add the Edge Function + frontend helper. Krava just provides the encrypted chat backend.

5 steps
  1. 01. Create a Lovable account

    Free. ~30 seconds. Already have a project? Skip to Step 04.

    Head to lovable.dev and sign up. You'll get a project canvas where you can prompt the AI to build apps.

    Open lovable.dev →
  2. 02. Paste the Krava starter prompt

    Tells Lovable to scaffold a chat app with passkey auth + encrypted memory.

    Build a [YOUR APP NAME] chat app with Krava AI.
    
    ──── INTEGRATION RULES (do not deviate) ────
    
    ★ INSTALL THE SDK FIRST: npm install @kravalabs/api-client
      This package exists on npm. Do not hand-roll API calls — import from it.
    
    ★ AUTH: Supabase passkey (WebAuthn) ONLY
      - NO email/password. NO magic link. NO OAuth.
      - Use Supabase's built-in passkey/WebAuthn provider for sign-up and sign-in.
      - The sign-in UI is a single "Sign in with passkey" button — no email field.
    
    Server-side (Supabase Edge Function at supabase/functions/krava-session/index.ts):
      - Import createKravaPlatformClient from @kravalabs/api-client
      - Verify the Supabase JWT via supabase.auth.getUser(), then call platform.users.getOrCreate(user.id)
      - Return { userToken } as JSON
      - Add CORS headers (see snippet below)
      - KRAVA_APP_KEY is server-only — never import it on the frontend
    
    Client-side (React):
      - After passkey sign-in, POST to /functions/v1/krava-session with Authorization: Bearer <supabaseAccessToken>
      - Store the returned userToken in React state
      - For chat: raw fetch SSE to https://krava.io/api/platform/chat
        Authorization: Bearer <userToken>   ← the token your Edge Function returned
        body: { message, chatId?, system? }
      - SSE stream: first event { chatId } — save it for the next turn
                    subsequent events { text } — append tokens to the last message bubble
                    [DONE] — stream complete
    
    Chat UI rules (prevents the first-message bug):
      1. On Send: optimistically append { role: "user", text } and { role: "assistant", text: "" }
      2. Fill the empty assistant bubble token-by-token as { text } events arrive
      3. Do NOT re-fetch the chat from the database after creating it — you already have the data
      4. chatId arrives in the FIRST SSE event — capture it with a useRef
    
    AI persona (inject as system param):
      "[YOUR PERSONA — e.g. You are a concise ADHD coach. Be warm and practical.]"
    
    Ask me only about visual style — the integration above is fully specified.

    Paste this prompt, then add a few sentences describing the app you want to build. The Krava part handles privacy; your part handles the product.

    Tip: if Lovable hits its time limit mid-build, just reply continue and it will pick up where it left off.

  3. 03. Get your Krava appKey

    Your server-side API key. Copy it from the platform and paste it into Lovable's environment variables.

    Get your API key

    Generate your KRAVA_APP_KEY

    Register your app on the Krava Platform, copy the App Key from the amber banner, then paste it directly into Lovable.

    1. 01
      Open /platform

      Opens in a new tab. Sign in with a passkey if it's your first time.

      First time? You'll see an “Identity Created” screen with a backup token — save it to your password manager immediately. It's your passkey recovery key and won't be shown again.

    2. 02
      Click + New app, give it a name, then click Create. An amber banner will appear — click Copy App Key.

      ⚠ Shown once only. Save this to a password manager, encrypted note, or print and store offline. If you lose it, register a new app.

    3. 03

      In Lovable, add it as an environment variable:

      Name: KRAVA_APP_KEY
      Value: paste your App Key here

      Go to Project Settings → Environment Variables in Lovable, then click Add Variable.

  4. 04. Confirm the integration code

    Lovable generated these files from your Step 2 prompt — confirm they exist, or paste them if anything is missing.

    Lovable already generated these files. Check that supabase/functions/krava-session/index.ts and src/lib/krava.ts exist in your project. If either is missing, paste the code below and tell Lovable to add it.

    Edge Function (Supabase) — runs server-side, keeps your appKey secret:

    // supabase/functions/krava-session/index.ts
    import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
    import { createKravaPlatformClient } from "https://esm.sh/@kravalabs/api-client@0.2.0";
    
    const cors = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "authorization, content-type",
    };
    
    const platform = createKravaPlatformClient({
      appKey: Deno.env.get("KRAVA_APP_KEY")!,
      baseUrl: "https://krava.io",
    });
    
    Deno.serve(async (req) => {
      if (req.method === "OPTIONS") {
        return new Response(null, { status: 204, headers: cors });
      }
    
      const supabase = createClient(
        Deno.env.get("SUPABASE_URL")!,
        Deno.env.get("SUPABASE_ANON_KEY")!,
        { global: { headers: { Authorization: req.headers.get("Authorization") ?? "" } } }
      );
    
      const { data: { user }, error } = await supabase.auth.getUser();
      if (error || !user) {
        return new Response("Unauthorized", { status: 401, headers: cors });
      }
    
      const { userToken } = await platform.users.getOrCreate(user.id);
      return Response.json({ userToken }, { headers: cors });
    });
    
    // After editing: supabase functions deploy krava-session --no-verify-jwt

    Frontend helper (React) — streams chat from the browser:

    // src/lib/krava.ts
    
    // Demo cache. In production, scope by userId and respect token TTL.
    let cachedToken: string | null = null;
    
    export async function getKravaToken(userId: string): Promise<string> {
      if (cachedToken) return cachedToken;
      const res = await fetch("/functions/v1/krava-session", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ externalUserId: userId }),
      });
      if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
      const { userToken } = await res.json();
      cachedToken = userToken;
      return userToken;
    }
    
    export async function* streamKravaChat(
      message: string,
      userId: string
    ): AsyncGenerator<string> {
      const userToken = await getKravaToken(userId);
      const res = await fetch("https://krava.io/api/platform/chat", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          // userToken is the per-user bearer token from POST /api/platform/users.
          // Safe in the browser — it is the user's own encryption key.
          Authorization: `Bearer ${userToken}`,
        },
        body: JSON.stringify({ message }),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body.error ?? `Chat failed (${res.status})`);
      }
      if (!res.body) throw new Error("Streaming not supported in this environment");
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buf = "";
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += decoder.decode(value, { stream: true });
        const lines = buf.split("\n");
        buf = lines.pop() ?? "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          const data = line.slice(6).trim();
          if (data === "[DONE]") return;
          try {
            const parsed = JSON.parse(data);
            if (parsed.text) yield parsed.text;
          } catch {
            // skip malformed SSE keep-alive frame
          }
        }
      }
    }
  5. 05. Deploy and verify

    Publish your app, open it in a new tab, send a message.

    ⚠ Test in a new browser tab, not the Lovable preview pane. WebAuthn (passkey creation) is blocked inside Lovable's preview iframe.

    Click Publish, then open the live URL in a new tab. Sign in with a passkey, send a message, and confirm a streamed response appears. That's it — your integration is working.

    Want a full 7-probe diagnostic? In Lovable's chat, type: “Run this in the terminal: KRAVA_APP_KEY=[your appKey] npx @kravalabs/api-client doctor Lovable has terminal access and will show you the output.

    You shipped. Your Lovable app has passkey identity, encrypted memory, and zero-retention inference. Tweet at us when it's live.