Eval Explained: Lichess Cloud Eval API with FEN and Python eval()
A senior developer's practical guide to the word "eval" in two worlds: chess engines and Python
Introduction
I have lost count of how many times a junior developer has asked me, "What does eval actually do?" The funny part is that the answer depends on who is asking. A chess player sees eval as the number next to the board. A Python developer sees python eval as a built-in that runs a string as code. Both are useful. Both can bite you if you treat them casually.
In this guide I'll walk through both meanings the way I'd explain them to my own team: a working example of the lichess cloud eval api fen call, how to read the response, and when python eval is the wrong tool. Every snippet below is something you can paste and run today.
https://lichess.org/api/cloud-eval, read pvs[0].cp for the score in centipawns, and never pass untrusted text into Python's eval().
What Does "Eval" Mean?
1. Eval in chess engines
In chess software, eval is the engine's numeric judgement of a position. It is usually shown in centipawns (1 pawn = 100 centipawns). A score of +120 means White is roughly 1.2 pawns better. A negative number favours Black. When a forced checkmate exists, engines report "mate in N" instead of a pawn score.
2. Eval in Python
The built-in eval() takes a string, parses it as a Python expression, runs it, and returns the result. Powerful, tiny, and risky. We'll cover that after the chess part.
Visual: how to read an eval bar
Left = Black advantage • Middle = equal • Right = White advantage. Values are centipawns divided by 100.
Lichess Cloud Eval API with a FEN
Lichess keeps a shared database of positions already analysed by strong engines. The endpoint returns a stored evaluation instantly, so you do not need to install or run Stockfish yourself. It needs no API token for basic use.
The request
You send a FEN (Forsyth-Edwards Notation, the text format that describes a chess position) as a query parameter:
GET https://lichess.org/api/cloud-eval
?fen=rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1
&multiPv=1
Remember to URL-encode the FEN. Spaces must become %20 or +. The Python requests library does that for you when you pass params.
Python example
import requests
API = "https://lichess.org/api/cloud-eval"
def cloud_eval(fen, multi_pv=1):
resp = requests.get(
API,
params={"fen": fen, "multiPv": multi_pv},
timeout=10,
)
if resp.status_code == 404:
return None # position not in the cloud cache
resp.raise_for_status() # catches 429 rate limits, 5xx, etc.
return resp.json()
fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
data = cloud_eval(fen)
if data:
best = data["pvs"][0]
print("Depth:", data["depth"])
print("Best line:", best["moves"])
print("Score (cp):", best.get("cp"))
else:
print("No cloud eval for this position")
The response
A typical reply looks like this (numbers will differ by position):
{
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
"knodes": 106325,
"depth": 35,
"pvs": [
{ "moves": "e7e5 g1f3 b8c6 ...", "cp": -18 }
]
}
| Field | Meaning |
|---|---|
depth | How deep the engine searched |
knodes | Thousands of positions searched |
pvs[].moves | Best line in UCI moves |
pvs[].cp | Centipawn score (White's point of view) |
pvs[].mate | Moves to mate, present instead of cp when forced |
Visual: request flow
Turn centipawns into a win percentage
Raw centipawns are hard for readers to feel. Lichess uses a logistic curve to convert them into a win chance, and you can reuse the same idea:
import math
def win_percent(cp):
return 50 + 50 * (2 / (1 + math.exp(-0.00368208 * cp)) - 1)
print(round(win_percent(100), 1)) # about 59.1 for White
print(round(win_percent(-300), 1)) # about 24.9 for White
Things that will trip you up
- 404 Not Found: the position simply is not in the cloud database. Rare or deep-in-game positions often miss. Fall back to a local engine if you need an answer every time.
- 429 Too Many Requests: you are being rate limited. Slow down, send one request at a time, and cache results.
- Point of view: read the sign carefully. Check your own results against a known position before you trust a pipeline.
- Variants: pass
variantif you are not analysing standard chess.
Python eval(): Useful, but Handle with Care
Now the other meaning. python eval evaluates a string as an expression:
print(eval("2 + 3 * 4")) # 14
x = 10
print(eval("x * 2")) # 20
It looks harmless until the string comes from a user. In code review, this is one of the first things I flag:
# DANGEROUS: never do this with untrusted input
user_text = "__import__('os').system('echo you have been hacked')"
eval(user_text)
That one line can run any command your process is allowed to run. A restricted globals dictionary does not make it safe either, because Python's internals offer many ways around it.
Safer alternatives
import ast, json
ast.literal_eval("[1, 2, {'a': 3}]") # only literals, no function calls
json.loads('{"cp": -18}') # best choice for API data
Notice the connection to our chess example: the Lichess reply is JSON, so use resp.json(). Reaching for eval() to parse an API response is a classic beginner mistake.
Visual: which tool to pick
| Your data | Use | Risk |
|---|---|---|
| API / JSON text | json.loads / resp.json() | Low |
| Python literal string | ast.literal_eval | Low-Medium |
| Untrusted expression | eval() | High, avoid |
Best Practices Checklist
- Always set a
timeouton network calls. - Cache each FEN result locally so you never ask for the same position twice.
- Handle
cpandmateseparately, since only one appears per line. - Treat
eval()as a last resort, never as a parser. - Log the FEN with every error, because it makes debugging far quicker.
Frequently Asked Questions (FAQ)
What is the Lichess cloud eval API?
It is a free endpoint that returns stored engine evaluations for chess positions you describe with a FEN, so you can get analysis without running an engine locally.
Why does the cloud eval API return 404?
The position has not been analysed and stored yet. This is normal for unusual positions. Use a local engine such as Stockfish as a fallback.
Do I need an API key for the Lichess cloud eval API?
No token is needed for this public endpoint, but you should respect rate limits and keep requests sequential.
What does a positive eval mean?
A positive centipawn value means White is better. A negative value means Black is better. Divide by 100 to get an approximate pawn advantage.
Is Python eval() safe?
Only with fully trusted input. For anything coming from users, files, or the network, use json.loads or ast.literal_eval instead.
What is a FEN string?
A single line of text that describes a chess position: piece placement, side to move, castling rights, en passant square, and move counters.
Conclusion
Whether you are building a chess analysis tool or tidying up a Python codebase, eval deserves respect. The lichess cloud eval api fen workflow gives you fast, free engine insight in about fifteen lines of code. And python eval is best kept for trusted, throwaway situations, with json and ast.literal_eval doing the real work.
Try the snippets with your own FEN strings, add a small cache, and you will have the core of a position analyser by tonight. If this guide saved you time, share it with a fellow developer, and drop your questions in the comments.