GuidesPROFILE APIS
LeetCode API in browser JavaScript
LeetCode has no documented public API, and the GraphQL endpoint its site uses, leetcode.com/graphql, sends no Access-Control-Allow-Origin header and answers preflights with 405. On 28 October 2026 all 6 public queries we tested, from solved counts and contest rating to the daily question, also worked as GET requests and returned 200 through the free cors.dev proxy with no signup. Keep it to public data: never send LeetCode cookies or a csrftoken through any proxy.
Why fetch() to leetcode.com/graphql fails
LeetCode has no documented public API. Its own site loads data from an undocumented GraphQL endpoint at https://leetcode.com/graphql, which can change without notice.
On 28 October 2026 no response from that endpoint carried Access-Control-Allow-Origin, for GET or POST, and the OPTIONS preflight answered 405 with {"errors":[{"message":"GraphQL only supports GET and POST requests."}]}. That breaks both ways of calling GraphQL from a page:
- A
POSTwithContent-Type: application/jsontriggers a preflight, hits the405, and is never sent. - A plain
GETwith the query in the URL needs no preflight and LeetCode answers200, but the browser hides the response because the CORS header is missing.
Both show up in the console as "blocked by CORS policy".
Public queries that work without a login
The endpoint accepts GraphQL over GET: the URL-encoded query in ?query= and, if it takes arguments, JSON in &variables=. Both forms answered 200, and so did the same URLs through the free cors.dev proxy (https://proxy.cors.dev/ plus the full URL), byte for byte.
| Query | Returns | Bytes | Result on 28 October 2026 |
|---|---|---|---|
| matchedUser profile and submitStats | Ranking, realName and accepted problems by difficulty | 245 | lee215: ranking 102,875, 692 solved: 127 Easy, 414 Medium, 151 Hard |
| userContestRanking | rating, globalRanking, attendedContestsCount, topPercentage | 119 | lee215: rating 2,204, rank 7,587, 27 contests, top 0.9% |
| recentAcSubmissionList | title, titleSlug, timestamp of recent accepted solves | 616 for 5 | lee215: 5 recent accepted problems |
| matchedUser userCalendar | streak, totalActiveDays, submissionCalendar | 993 | lee215: streak 3, 47 active days |
| activeDailyCodingChallengeQuestion | date, link, question title and difficulty | 188 | Minimum XOR Path in a Grid, Medium |
| allQuestionsCount | Total problems by difficulty | 171 | 4,096 problems: 974 Easy, 2,136 Medium, 986 Hard |
Quirks to handle when you parse the responses:
- Usernames match case-insensitively: "leetcode" returned the account "LeetCode". An unknown username still gets HTTP
200, withdata.matchedUserset tonulland "That user does not exist." inerrors[0].message. userContestRankingisnullfor accounts that never entered a contest, including the official "LeetCode" account.userCalendar.submissionCalendaris a JSON string inside the JSON, mapping Unix day timestamps to submission counts. For lee215 it held 47 days, matchingtotalActiveDays.recentAcSubmissionListreturnstimestampas a string of Unix seconds, not a number.
Show your stats through the free proxy
The free proxy needs no signup and allows GET and HEAD at about 600 requests a minute per IP, with no caching and no cookies forwarded. A POST through it gets 405 method_not_allowed, so use the GET form.
GraphQL lets you ask for several root fields at once, so one request can fetch matchedUser (ranking, solved counts, streak), userContestRanking and allQuestionsCount. The snippet throws on an unknown user and handles a null contest ranking; for lee215 it printed "All: 692 of 4096", "Hard: 151 of 986" and "Rating 2204 Streak 3".
const query = `
query profile($username: String!) {
matchedUser(username: $username) {
profile { ranking }
submitStats { acSubmissionNum { difficulty count } }
userCalendar { streak totalActiveDays }
}
userContestRanking(username: $username) { rating globalRanking attendedContestsCount }
allQuestionsCount { difficulty count }
}`;
const params = new URLSearchParams({ query, variables: JSON.stringify({ username: 'lee215' }) });
const response = await fetch(`https://proxy.cors.dev/https://leetcode.com/graphql?${params}`);
if (!response.ok) throw new Error(`${response.status} ${response.headers.get('X-Cors-Error') ?? ''}`);
// LeetCode answers 200 for an unknown user too, with the reason in errors
const { data, errors } = await response.json();
if (!data?.matchedUser) throw new Error(errors?.[0]?.message ?? 'No such user');
const totals = Object.fromEntries(data.allQuestionsCount.map(({ difficulty, count }) => [difficulty, count]));
for (const { difficulty, count } of data.matchedUser.submitStats.acSubmissionNum) {
console.log(`${difficulty}: ${count} of ${totals[difficulty]}`);
}
// userContestRanking is null for accounts that never entered a contest
const contest = data.userContestRanking;
console.log('Rating', contest ? Math.round(contest.rating) : 'none', 'Streak', data.matchedUser.userCalendar.streak);Rate limits and caching
LeetCode publishes no rate limit for leetcode.com/graphql. In our test, 60 direct requests sent 10 at a time all returned 200, but a portfolio card does not need fresh numbers on every page view.
Cache in the browser. Every response sets a csrftoken cookie with Set-Cookie, and cors.dev never caches a response that sets a cookie, so the X-Cors-Cache header on cors.dev Pro would report BYPASS here. The snippet keeps each result in localStorage for 30 minutes and falls back to the saved copy when a request fails.
const CACHE_MINUTES = 30;
async function leetcodeStats(username) {
const cacheKey = `leetcode:${username.toLowerCase()}`;
const saved = JSON.parse(localStorage.getItem(cacheKey) ?? 'null');
if (saved && Date.now() - saved.savedAt < CACHE_MINUTES * 60_000) return saved.user;
const params = new URLSearchParams({
query: 'query ($username: String!) { matchedUser(username: $username) { submitStats { acSubmissionNum { difficulty count } } } }',
variables: JSON.stringify({ username }),
});
try {
const response = await fetch(`https://proxy.cors.dev/https://leetcode.com/graphql?${params}`);
if (!response.ok) throw new Error(`${response.status} ${response.headers.get('X-Cors-Error') ?? ''}`);
const { data, errors } = await response.json();
if (!data?.matchedUser) throw new Error(errors?.[0]?.message ?? 'No such user');
localStorage.setItem(cacheKey, JSON.stringify({ savedAt: Date.now(), user: data.matchedUser }));
return data.matchedUser;
} catch (error) {
// An old card beats an empty one when LeetCode or the network fails
if (saved) return saved.user;
throw error;
}
}
const user = await leetcodeStats('lee215');
console.log(user.submitStats.acSubmissionNum);The free tier is enough for LeetCode: the GET form needs no key, and each visitor's requests count against their own IP's limit of about 600 a minute. cors.dev Pro ($5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card at cors.dev/signup) is for APIs that need POST, an Authorization header or custom headers, at 3,000 requests a minute per account and 100 in flight.
Login-only data and LeetCode's terms
Your submitted code, private lists and account settings need the LEETCODE_SESSION cookie and the csrftoken. Never put them in page code and never send them through any proxy. The free proxy dropped both a Cookie header and x-csrftoken in our test and listed them in X-Cors-Dropped-Headers.
LeetCode's Terms of Service forbid "crawling," "scraping," or "spidering" any part of the Service, and any process that places "an undue load on its infrastructure". A widget that reads your own public profile and caches it is a different thing from a crawler, but LeetCode decides what counts, so keep requests few and cached and do not crawl users or problems.
Good questions.
Does LeetCode have an official API?
No. LeetCode documents no public API; its site uses an undocumented GraphQL endpoint at https://leetcode.com/graphql that can change without notice. Public profile stats, contest ratings, recent solves and the daily question all work without a login.
How do I get a LeetCode user's solved problem count?
Query matchedUser(username:) with submitStats { acSubmissionNum { difficulty count } }, and add allQuestionsCount for the site-wide totals. On 28 October 2026 lee215 had 692 accepted problems out of 4,096.
Why does fetch() to leetcode.com/graphql give a CORS error?
LeetCode sends no Access-Control-Allow-Origin header, and a POST with a JSON body is stopped earlier, because the OPTIONS preflight gets 405. Send the query as a GET through https://proxy.cors.dev/ and the browser gets the CORS header it needs.
How do I show my LeetCode stats on my portfolio site?
Build the query and variables with URLSearchParams and send one GET to https://proxy.cors.dev/https://leetcode.com/graphql. Keep the result in localStorage and refetch after about 30 minutes; cors.dev cannot cache it for you, because LeetCode sets a cookie on every response.
How do I get the LeetCode daily question with JavaScript?
Query activeDailyCodingChallengeQuestion { date link question { title difficulty } } as a GET through the proxy. It needs no username or login; on 28 October 2026 it returned "Minimum XOR Path in a Grid", Medium.
Can I fetch my own submissions or code from the LeetCode API?
Not from a page. Submitted code needs your LEETCODE_SESSION cookie and csrftoken, which belong only on your own machine, never in browser code or a proxy. For a public list of your recent solves, use recentAcSubmissionList.