⌥ API Reference
Public HTTP endpoints
The endpoints below power our own surfaces (search, leaderboards, profile pages). They're publicly callable — no API key for read-only routes. Auth-required routes use the same Supabase session cookie our web app does; if you want third-party access we can issue a bearer token; contact us.
Search
/api/search?q=<query>&kind=<surface?>PUBLICFull-text search across lessons, glossary, cheatsheets, guides, projects, and learning paths. Scores title > module > body, with small per-surface boost. Top 30 with <mark>-wrapped snippet. Optional `&kind=lesson|glossary|cheatsheet|guide|project|path` filter.
REQUEST
# query length must be between 2 and 80 chars curl "https://learnpython.academy/api/search?q=list+comprehension"
RESPONSE
{
"results": [
{
"id": "lesson-66",
"title": "List comprehensions",
"url": "/lesson/lesson-66",
"kind": "lesson",
"trackId": "foundations",
"trackName": "Python Foundations",
"moduleTitle": "Module 4 · Strings & comprehensions",
"type": "write",
"is_free": false,
"score": 15,
"snippet": "…use <mark>list comprehension</mark> instead of map+lambda…"
}
],
"total": 13
}- Edge-cached 5 minutes.
- No API key needed.
- Index covers 1,400+ lessons + 60 glossary terms + 8 cheatsheets + 7 guides + 12 projects + 9 paths.
Leaderboards
/api/leaderboardPUBLICTop 50 learners by total XP. Only display_name + counters — no PII.
REQUEST
curl "https://learnpython.academy/api/leaderboard"
RESPONSE
{
"entries": [
{ "id": "uuid", "display_name": "Ada K.", "xp_total": 12450, "streak_days": 18 },
…
]
}- Edge-cached 60s.
- Requires migration 20260517_public_leaderboard.sql to be applied.
/api/challenges/leaderboard?challenge_id=<slug>PUBLICPer-challenge ranking. Score column varies by kind (perf=ms ASC, golf=chars ASC, speed=correct DESC).
REQUEST
curl "https://learnpython.academy/api/challenges/leaderboard?challenge_id=perf-fizzbuzz"
RESPONSE
{
"entries": [
{
"user_id": "uuid",
"display_name": "Bob M.",
"execution_ms": 0.74,
"correct_count": null,
"char_count": null,
"rank": 1,
"created_at": "2026-05-18T14:23:00Z"
}
]
}- Edge-cached 30s.
Profiles
/u/<handle>PUBLICHTML profile page for an opted-in learner. Returns 404 when handle missing or profile not public.
REQUEST
# HTML page — no JSON variant. Use it in an iframe or scrape. curl "https://learnpython.academy/u/ada-k"
RESPONSE
<!doctype html>…
Feeds
/blog/rss.xmlPUBLICRSS 2.0 feed of every blog post. Auto-discovered by Feedly / NetNewsWire / Reeder.
REQUEST
curl "https://learnpython.academy/blog/rss.xml"
RESPONSE
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0">…
/sitemap.xmlPUBLICStandard XML sitemap covering 1000+ URLs — lessons, tracks, paths, projects, blog, public profile pages.
REQUEST
curl "https://learnpython.academy/sitemap.xml"
RESPONSE
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">…
Glossary
/api/glossaryPUBLICFlat list of every Python term in the CodeMentor glossary — definition, category, linked lesson, public URL. CORS open, edge-cached 1 hour.
REQUEST
curl "https://learnpython.academy/api/glossary"
RESPONSE
{
"items": [
{
"term": "Decorator",
"slug": "decorator",
"definition": "A higher-order function that wraps another to add behavior — logging, caching, auth. `@cache` is one.",
"category": "Functions",
"lesson": "lesson-115",
"url": "https://learnpython.academy/glossary/decorator"
}
],
"total": 60
}- CORS open.
- Edge-cached 1 hour.
/api/glossary/<slug>PUBLICSingle term by slug. Same shape as one item from /api/glossary, plus an `example` code snippet when available.
REQUEST
curl "https://learnpython.academy/api/glossary/decorator"
RESPONSE
{
"term": "Decorator",
"slug": "decorator",
"definition": "A higher-order function that wraps another…",
"category": "Functions",
"lesson": "lesson-115",
"example": "@functools.cache\ndef fib(n): ...",
"url": "https://learnpython.academy/glossary/decorator"
}- 404 if slug not in the glossary.
Cheatsheets
/api/cheatsheetPUBLICIndex of every cheatsheet — title, group count, entry count, public URL. CORS open, edge-cached 1 hour.
REQUEST
curl "https://learnpython.academy/api/cheatsheet"
RESPONSE
{
"items": [
{
"slug": "fastapi",
"title": "FastAPI cheatsheet — routes, Pydantic, auth, async, deploy",
"track_id": "fastapi",
"emoji": "⚡",
"updated": "2026-05",
"group_count": 6,
"entry_count": 17,
"url": "https://learnpython.academy/cheatsheet/fastapi"
}
],
"total": 8
}/api/cheatsheet/<slug>PUBLICFull structured cheatsheet body — every group + every snippet (label, why, code).
REQUEST
curl "https://learnpython.academy/api/cheatsheet/fastapi"
RESPONSE
{
"slug": "fastapi",
"title": "FastAPI cheatsheet — …",
"groups": [
{
"title": "Hello + routing",
"entries": [
{ "label": "Minimal app", "why": "Spins up a server immediately.", "code": "from fastapi import FastAPI\napp = FastAPI()\n…" }
]
}
]
}- 404 if slug not in the cheatsheet collection.
Embeds
/api/embed/lesson/<id>PUBLICJSON metadata for a single lesson. Response is gated by `is_free`: free lessons return the full task text and (for quiz/predict) the option list. Paid lessons return only title + type + a short teaser — no task body, no quiz options, no answer key. To validate an answer, POST to /api/check-answer (paywall enforced there too).
REQUEST
curl "https://learnpython.academy/api/embed/lesson/lesson-01"
RESPONSE
# free lesson — full task + options (no correctIndex, ever)
{
"id": "lesson-01",
"title": "Hello, World!",
"type": "write",
"is_free": true,
"xp_reward": 75,
"task": "Print Hello, World!",
"track": { "id": "foundations", "name": "Python Foundations", "emoji": "🐍" },
"quiz": null,
"embed_url": "https://learnpython.academy/embed/lesson/lesson-01",
"lesson_url": "https://learnpython.academy/lesson/lesson-01"
}
# paid lesson — task replaced with teaser, quiz null
{
"id": "lesson-66",
"title": "**kwargs — keyword arguments",
"type": "write",
"is_free": false,
"xp_reward": 100,
"task": "This lesson requires a Pro trial. Open the lesson on learnpython.academy to read the full task — first 15 Foundations lessons are free, no signup.",
"track": { "id": "foundations", "name": "Python Foundations", "emoji": "🐍" },
"quiz": null,
"embed_url": "https://learnpython.academy/embed/lesson/lesson-66",
"lesson_url": "https://learnpython.academy/lesson/lesson-66"
}- CORS open (Access-Control-Allow-Origin: *).
- Edge-cached 5 minutes.
- `correctIndex` is NEVER returned by this endpoint. Use POST /api/check-answer with {lessonId, optionIndex} — that endpoint returns {correct: bool, explanation?} and enforces Pro auth for paid lessons.
/embed/lesson/<id>PUBLICIframe-ready HTML page for a single lesson. No nav, no footer, minimal UI. Same is_free gating as the JSON endpoint: free lessons show the full task and a working quiz check flow; paid lessons show a compact 'Pro track' teaser panel with a CTA to open the full lesson on learnpython.academy. Frame-ancestors *, so it embeds anywhere.
REQUEST
<iframe src="https://learnpython.academy/embed/lesson/lesson-01" width="100%" height="520" style="border:1px solid var(--border);border-radius:12px" ></iframe>
RESPONSE
<!doctype html>…
- Free lesson → task + check-answer widget rendered.
- Paid lesson → teaser only; no task text or quiz options in the HTML.
- Includes a 'Try full lesson →' link back to /lesson/<id>?ref=embed.
- Resize via the iframe height attribute; the embed is responsive.
User data (auth required)
/api/progressAUTHList of lesson IDs the authenticated user has completed.
REQUEST
# Auth via Supabase session cookie. No external API key.
RESPONSE
{ "completed": ["lesson-1", "lesson-2", …] }/api/bookmarksAUTHUser's saved-for-later lessons.
REQUEST
# Auth required.
RESPONSE
{ "bookmarks": [{ "lesson_id": "lesson-66", "created_at": "…" }] }/api/achievementsAUTHUnlocked badges + the snapshot of metrics used to compute them.
REQUEST
# Auth required.
RESPONSE
{
"badges": [{ "id": "ten-lessons", "tier": "bronze", "icon": "📚" }],
"snapshot": {
"completed_count": 12,
"xp_total": 850,
"streak_days": 4,
"missions_completed": 1,
…
}
}/api/bookmarksAUTHAdd a bookmark.
REQUEST
curl -X POST "https://learnpython.academy/api/bookmarks" \
-H "Content-Type: application/json" \
-d '{"lesson_id": "lesson-66"}'RESPONSE
{ "ok": true }Need a webhook? Bulk export? Bearer-token API access? Email support@learnpython.academy and tell us what you're building — we'll wire something up.
Rate limits: ~60 reqs/min/IP on /api/search; everything else is edge-cached. Abuse → throttled or blocked.