npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@udondan/duolingo

v1.0.0

Published

Duolingo API client library and MCP server

Readme

@udondan/duolingo

A TypeScript package that provides both a Duolingo API client library and an MCP server for LLM agents (e.g. Claude).

Built natively in TypeScript against the unofficial Duolingo REST API — no third-party Duolingo library dependency.

Table of Contents


Getting Your JWT Token

Both the MCP server and the library require a Duolingo JWT token for authentication.

  1. Log in to duolingo.com in your browser.
  2. Open the browser developer console (F12 → Console tab).
  3. Run:
    document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11)
  4. Copy the output — that is your JWT token.

Note: JWT tokens expire. If you get authentication errors, repeat the steps above.


MCP Server

MCP Installation

Option A — run directly with npx (no install needed):

DUOLINGO_USERNAME=your_username DUOLINGO_JWT=your_jwt npx @udondan/duolingo

Option B — install globally:

npm install -g @udondan/duolingo
DUOLINGO_USERNAME=your_username DUOLINGO_JWT=your_jwt duolingo-mcp

Option C — clone and build from source:

git clone https://github.com/udondan/duolingo.git
cd duolingo
npm install && npm run build
node dist/server.js

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "duolingo": {
      "command": "npx",
      "args": ["-y", "@udondan/duolingo"],
      "env": {
        "DUOLINGO_USERNAME": "your_username",
        "DUOLINGO_JWT": "your_jwt_token"
      }
    }
  }
}

Claude Code

claude mcp add duolingo -- npx -y @udondan/duolingo

Then set the environment variables before starting Claude Code:

export DUOLINGO_USERNAME="your_username"
export DUOLINGO_JWT="your_jwt_token"

Available Tools

All tools are read-only — the server never modifies your Duolingo account.

Account

| Tool | Description | |------|-------------| | duolingo_get_user_info | Profile: username, name, bio, location, avatar, followers, learning language | | duolingo_get_settings | Notification and account settings | | duolingo_get_streak_info | Current streak, longest streak, daily goal, extended today | | duolingo_get_daily_xp_progress | XP goal, XP earned today, lessons completed today | | duolingo_get_languages | Languages being learned (full names or abbreviations) | | duolingo_get_courses | All courses including Math, Chess, and Music with XP per course | | duolingo_get_friends | Users the authenticated user follows, with total XP | | duolingo_get_calendar | Recent activity calendar for the current course (~last 2 weeks) | | duolingo_get_leaderboard | Authenticated user's friends sorted by XP for week or month | | duolingo_get_shop_items | Full shop catalogue with prices and item types | | duolingo_get_health | Current hearts count, max hearts, refill timing | | duolingo_get_currencies | Gem and lingot balances | | duolingo_get_streak_goal | Current streak goal with upcoming checkpoints |

Language

| Tool | Description | |------|-------------| | duolingo_get_language_details | Level, points, streak for a specific language | | duolingo_get_language_progress | Detailed progress: level %, points to next level, fluency | | duolingo_get_known_topics | Learned topic/skill names | | duolingo_get_unknown_topics | Not-yet-learned topics | | duolingo_get_golden_topics | Fully mastered topics (strength = 1.0) | | duolingo_get_reviewable_topics | Learned but not fully mastered topics | | duolingo_get_known_words | Set of known words for a language | | duolingo_get_learned_skills | Full skill objects sorted by learning order | | duolingo_get_language_voices | Available TTS voice names for a language | | duolingo_get_audio_url | Pronunciation audio URL for a word |

Utilities

| Tool | Description | |------|-------------| | duolingo_get_language_from_abbr | Convert language abbreviation to full name (e.g. frFrench) | | duolingo_get_abbreviation_of | Convert full language name to abbreviation (e.g. Frenchfr) |


Library

Library Installation

npm install @udondan/duolingo

Quick Start

import { DuolingoClient } from '@udondan/duolingo';

const client = new DuolingoClient('your_username', 'your_jwt_token');

// Get all courses including Math, Chess, and Music
const userData = await client.getUserData();
const userId = userData.id;
const v2 = await client.getUserDataV2(userId);
for (const course of v2.courses) {
  console.log(`${course.subject}: ${course.xp} XP`);
}

// Get streak info
const streak = v2.streak;
const longestStreak = v2.streakData.longestStreak?.length;

// Get friends
const friends = await client.getFollowing(userId);
for (const friend of friends) {
  console.log(`${friend.username}: ${friend.totalXp} XP`);
}

// Get shop items
const items = await client.getShopItems();
const streakFreeze = items.find(i => i.id === 'streak_freeze');

// Get hearts
const health = await client.getHealth();
console.log(`Hearts: ${health.hearts}/${health.maxHearts}`);

API Reference

new DuolingoClient(username, jwt)

Creates a new client instance. Results are cached per instance.

const client = new DuolingoClient('your_username', 'your_jwt_token');

User Data

// Legacy API — returns language_data with skills, calendar, etc.
// Required for: language details, topics, known words, learned skills
const userData = await client.getUserData(username?);

// 2023-05-23 API — returns all courses (language + math/chess/music),
// richer streak data, subscriber level, gems, health
const userId = userData.id;
const v2 = await client.getUserDataV2(userId);

// Resolve a username to a numeric user ID
const id = await client.getUserIdByUsername('someuser');

Courses (all subjects)

const v2 = await client.getUserDataV2(userId);

// All courses
v2.courses;  // DuolingoCourse[]

// Filter by subject
const langCourses  = v2.courses.filter(c => c.subject === 'language');
const mathCourses  = v2.courses.filter(c => c.subject === 'math');
const chessCourses = v2.courses.filter(c => c.subject === 'chess');
const musicCourses = v2.courses.filter(c => c.subject === 'music');

Each course has: id, subject, topic, xp, fromLanguage. Language courses also have: learningLanguage, title, authorId.

Daily XP Progress

const progress = await client.getUserDataById(userId, ['xpGoal', 'xpGains', 'streakData']);
// progress.xpGoal    — daily XP goal
// progress.xpGains   — array of { xp, skillId, time } for recent lessons
// progress.streakData.updatedTimestamp — last streak update

Friends & Social

// People this user follows (= friends)
const following = await client.getFollowing(userId);
// following[0].username, .totalXp, .userScore?.score (weekly XP), .isFollowedBy

// People who follow this user
const followers = await client.getFollowers(userId);

Shop, Health & Currencies

// Full shop catalogue (read-only)
const items = await client.getShopItems();
// items[0].id, .name, .type, .price, .currencyType, .lastUsedDate

// Hearts / health (authenticated user only)
const health = await client.getHealth();
// health.hearts, .maxHearts, .eligibleForFreeRefill, .secondsUntilNextHeartSegment

// Gem and lingot balances (authenticated user only)
const { gems, lingots } = await client.getCurrencies();

Streak Goals

// Current streak goal with checkpoints
const goal = await client.getStreakGoalCurrent();
// goal.hasActiveGoal, goal.streakGoal.lastCompleteGoal, .checkpoints, .nextSelectedGoal

// Available next goal options
const options = await client.getStreakGoalNextOptions();

TTS Audio

// Discover available voices for a language
const voices = await client.getLanguageVoices('es');  // ['beaes', 'juniores', ...]

// Build an audio URL
const url = await client.buildAudioUrl('hola', 'es');
const urlWithVoice = await client.buildAudioUrl('hola', 'es', 'beaes');

Error Handling

import {
  DuolingoClient,
  DuolingoAuthError,
  DuolingoNotFoundError,
  DuolingoCaptchaError,
  DuolingoClientError,
} from '@udondan/duolingo';

try {
  const data = await client.getUserData('someuser');
} catch (err) {
  if (err instanceof DuolingoAuthError) {
    // JWT expired — extract a new one from the browser
  } else if (err instanceof DuolingoNotFoundError) {
    // User does not exist
  } else if (err instanceof DuolingoCaptchaError) {
    // Duolingo blocked the request — try again later
  } else if (err instanceof DuolingoClientError) {
    // Other API error
  }
}

Development

git clone https://github.com/udondan/duolingo.git
cd duolingo
npm install

# Build
npm run build

# Run tests (unit + integration)
npm test

# Integration tests require credentials:
export DUOLINGO_USERNAME="your_username"
export DUOLINGO_JWT="your_jwt_token"
npm test

# Type-check without building
npm run typecheck

# Run MCP server in dev mode (no build step)
npm run dev

Project Structure

src/
├── index.ts           # Library entry point — exports client, types, errors
├── server.ts          # MCP server entry point (stdio transport)
├── client/
│   ├── duolingo.ts    # DuolingoClient — all API methods
│   ├── types.ts       # TypeScript interfaces for all API responses
│   └── errors.ts      # Custom error classes
└── tools/
    ├── account.ts     # Account tools (13)
    ├── language.ts    # Language tools (11)
    ├── shop.ts        # Utility tools (2)
    └── helpers.ts     # Shared utilities (error handling, Zod schemas)

API Versions Used

| Endpoint | API Version | Used For | |----------|-------------|----------| | /users/<username> | Legacy | User profile, language data, skills, calendar | | /2023-05-23/users/{id} | Current | Courses (all subjects), streak, health, gems | | /2023-05-23/friends/users/{id}/following | Current | Friends, leaderboard | | /2023-05-23/shop-items | Current | Shop catalogue | | /users/{id}/streak-goal-current | Current | Streak goals | | /2017-06-30/sessions | Current | TTS voice discovery |


License

MIT — see LICENSE