Point Kwazi at your own notes, past papers and web pages. Learners snap or type a question and get grade-appropriate, step-by-step answers and focused lessons that follow your material and South Africa's CAPS curriculum.
Live widget running on a sample answer. Type anything or tap a step's “Break it down”.
Three steps. Your secret key stays on your server; the browser only ever gets a short-lived session token.
Sign in at app.kwazi.kvra.co.za, open Me → Developer API and create a secret key (kwz_sk_…).
Send public https links to PDFs, web pages or text. Kwazi fetches each once and keeps a copy to teach from.
Call the API for answers and focused lessons, or mint a widget session and drop the chat widget into your page.
Four small packages with no runtime dependencies, all in one public repository.
An animated, mobile-first <kwazi-chat> web component: photo questions, steps one at a time, “break it down”, check-yourself and confetti.
npm install kwazi-widget
<!-- or straight from a CDN -->
<script src="https://cdn.jsdelivr.net/npm/kwazi-widget@0.2/dist/kwazi-widget.js" defer></script>
<kwazi-chat session-endpoint="/api/kwazi-session"></kwazi-chat>
Typed client for Node 18+ using the built-in fetch.
npm install kwazi
Standard-library client for Python 3.9+.
pip install https://github.com/kvra-global/kwazi-sdk/releases/download/v0.2.1/kwazi-0.2.1-py3-none-any.whl
Java 17+ client with no dependencies, for Android back ends and JVM services.
repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.kvra-global:kwazi-sdk:v0.2.1") }
Register a source, get a focused lesson, and mint a widget session. Set KWAZI_API_KEY first.
import { Kwazi } from "kwazi";
const kwazi = new Kwazi(); // reads KWAZI_API_KEY
const notes = await kwazi.sources.create({
url: "https://your-school.example/grade-7/fractions.pdf",
subject: "mathematics",
grade: 7
});
const lesson = await kwazi.lessons.create({
grade: 7, subject: "mathematics", topic: "Adding fractions", sourceIds: [notes.id]
});
console.log(lesson.steps.map((step) => step.title), lesson.finalAnswer);
// In your web server: a session for the chat widget
app.post("/api/kwazi-session", async (req, res) => {
const session = await kwazi.widgetSessions.create({ sourceIds: [notes.id], grade: 7, subject: "mathematics" });
res.json({ token: session.token });
});from kwazi import Kwazi
kwazi = Kwazi() # reads KWAZI_API_KEY
notes = kwazi.sources.create("https://your-school.example/grade-7/fractions.pdf", subject="mathematics", grade=7)
lesson = kwazi.lessons.create(7, "mathematics", "Adding fractions", source_ids=[notes["id"]])
print([step["title"] for step in lesson["steps"]], lesson["finalAnswer"])
session = kwazi.widget_sessions.create(source_ids=[notes["id"]], grade=7, subject="mathematics")
# return {"token": session["token"]} from your /api/kwazi-session routeimport za.co.kvra.kwazi.Kwazi;
import java.util.List;
Kwazi kwazi = Kwazi.builder().build(); // reads KWAZI_API_KEY
Kwazi.Source notes = kwazi.createSource(
new Kwazi.SourceInput("https://your-school.example/grade-7/fractions.pdf").subject("mathematics").grade(7));
Kwazi.Answer lesson = kwazi.createLesson(
new Kwazi.LessonInput(7, "mathematics", "Adding fractions").sourceIds(List.of(notes.id())));
System.out.println(lesson.finalAnswer());
Kwazi.WidgetSession session = kwazi.createWidgetSession(
new Kwazi.WidgetSessionInput().sourceIds(List.of(notes.id())).grade(7).subject("mathematics"));curl https://api.kwazi.kvra.co.za/v1/sources \
-H "Authorization: Bearer $KWAZI_API_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://your-school.example/grade-7/fractions.pdf","subject":"mathematics","grade":7}'
curl https://api.kwazi.kvra.co.za/v1/questions \
-H "Authorization: Bearer $KWAZI_API_KEY" -H "Content-Type: application/json" \
-d '{"grade":7,"subject":"mathematics","question":"What is 1/2 + 1/4?","sourceIds":["SOURCE_ID"]}'<!-- Floating launcher; your /api/kwazi-session route returns {"token": "kwz_ws_…"} -->
<script src="https://kvra-global.github.io/kwazi-sdk/widget/kwazi-widget.js" defer></script>
<kwazi-chat session-endpoint="/api/kwazi-session" heading="Ask Kwazi" accent="#0F766E"></kwazi-chat>
<!-- Or inline, from your own bundle -->
<div id="tutor" style="height:600px"></div>
<script type="module">
import { mount } from "kwazi-widget";
mount({
target: document.getElementById("tutor"),
inline: true,
getToken: async () => (await (await fetch("/api/kwazi-session", { method: "POST" })).json()).token
});
</script>Base URL https://api.kwazi.kvra.co.za. Full schema: openapi.json (OpenAPI 3.1, generated from the same contracts the API validates with).
| Call | Auth | What it does |
|---|---|---|
POST /v1/sources | secret key | Register a public https PDF (up to 6 MB), web page or text file. Kwazi fetches it once; failures are saved with a reason. |
GET /v1/sources | secret key | List your sources (up to 200 per account). |
DELETE /v1/sources/{id} | secret key | Delete a source and Kwazi's copy of it. |
POST /v1/questions | secret key | Ask a typed question and/or attach up to three photos or PDFs, grounded in up to three sources. |
POST /v1/lessons | secret key | A focused lesson on one topic: worked example, steps and a practice question. |
GET /v1/usage | secret key | Your plan's limits and what this key has used today. |
POST /v1/widget/sessions | secret key | Mint a widget token (1 minute to 24 hours) that fixes the sources, grade and subject. |
POST /v1/widget/questions | widget token | Used by the widget; open to any origin, no cookies. |
Limits follow the plan of the account that owns the key. Widget sessions count against their key. Check where you stand any time with GET /v1/usage (kwazi.usage()). Over a limit you get HTTP 429 (or 409 for keys and sources) with a message you can show, and a Retry-After header for per-minute limits.
| Plan | Keys | Sources | Requests a minute per key | Questions a day per key |
|---|---|---|---|---|
| Free | 1 | 5 | 10 | 5 (the account has 5 free questions in total) |
| Kwazi Learner | 3 | 25 | 30 | 60 |
| Kwazi Family | 5 | 50 | 30 | 200 |
| Enterprise | 10 | 200 | 60 | 1,000 |
Identical requests (same question, grade, subject and sources) are answered from a cache for a day, and lessons for a week, so popular lessons come back instantly. The x-kwazi-cache response header says hit or miss. Cached answers still count towards your limits.
kwz_sk_ keys on your server. If one leaks, revoke it under Me → Developer API; its widget sessions stop at once.