← Developer guides

Integrate into your own server

Add a token-minting endpoint to your backend so the browser SDK gets a short-lived session JWT — no secret ever reaches the client.

This is the pattern from Choose your auth mode made concrete: a token-minting endpoint on your server, and a browser page that calls it before calling the SDK.

1. Add a token endpoint to your backend

import jwt from "jsonwebtoken";

const SECRET = process.env.QURAVIN_SIGNING_SECRET;   // from Get your API key
const APP_ID = "your-app-id";                        // your app's registered id

app.get("/ai-token", requireUserLogin, (req, res) => {
  const token = jwt.sign(
    {
      iss: APP_ID,
      sub: req.user.id,                    // your app's user id
      aud: "quravin",
      pipelines: ["translate-string"],     // which pipelines this user may call
      rate_limit: { rpm: 30 },
    },
    SECRET,
    { algorithm: "HS256", expiresIn: "15m" },
  );
  res.json({ token, expires_in: 15 * 60 });
});

requireUserLogin is your own auth middleware — the token only ever goes to a user your server has already authenticated.

2. Fetch the token and call the SDK from the browser

<input id="text" />
<button id="go">Translate</button>
<span id="result"></span>

<script src="https://js.quravin.com/v1.js"></script>
<script>
  const fetchToken = async () => {
    const r = await fetch("/ai-token", { credentials: "include" });
    if (!r.ok) throw new Error("Token fetch failed");
    return (await r.json()).token;
  };

  const ai = new Quravin.Quravin({
    endpoint: "https://api.example.com/ai-pipeline",
    sessionToken: await fetchToken(),
    onTokenExpired: fetchToken,   // auto-refreshes when the API returns 401
  });

  document.getElementById("go").onclick = async () => {
    const out = await ai.run({
      pipeline: "translate-string",
      inputs: { text: document.getElementById("text").value, target_language: "German" },
    });
    document.getElementById("result").textContent = out.translation;
  };
</script>

3. Handle errors

A quota or rate-limit hit returns a typed error — show the user a clear message and a link to pricing:

try {
  const out = await ai.run({ pipeline: "translate-string", inputs: { /* ... */ } });
} catch (err) {
  showToast("Translation failed: " + err.message);
}

That’s the whole loop: your server mints tokens, the browser never sees a secret, and every call is attributed to the user who made it. The SDK also has a more complete reference — batching with ai.runMany, the ai.button DOM binding, raw-prompt mode, and a full troubleshooting table — ask your platform contact for it if you need to go deeper.