API
No key, no meaningful rate limit, CORS open to everything. Every response describes the connection it arrived on, so the client you call it with is the client you measure.
Endpoints
| Path | Returns |
|---|---|
| /api/all | Everything: decoded TLS, HTTP/2 frames, JA3, JA4 |
| /api/clean | Just the hashes. The one to assert against in CI |
| /api/tls | ClientHello detail only |
| /api/http2 | SETTINGS, WINDOW_UPDATE, PRIORITY, pseudo-header order |
| /api/http1 | HTTP/1.1 headers in wire order, original casing |
| /api/ja3 | JA3 hash, plain text |
| /api/ja3/full | Unhashed JA3 string |
| /api/ja4 | JA4 fingerprint, plain text |
| /api/ja4/raw | JA4 with both hash halves expanded |
| /api/akamai | Akamai HTTP/2 fingerprint, plain text |
| /api/ip | Your IP |
Add ?pretty=0 for compact JSON. Responses are never cached, because a cached fingerprint would belong to someone else.
Check a fingerprint
curl -s https://kittens.sh/api/clean{
"ja3_hash": "375c6162a492dfbf2795909110ce8424",
"ja4": "t13d4907h2_0d8feac7bc37_7395dae3b2f3",
"akamai_hash": "64a832f547be33249bf4d33e8a46c5dc",
"user_agent": "curl/8.7.1"
}Assert in CI
The usual reason to automate this: proving an impersonation library still matches its target after a dependency bump.
EXPECTED="t13d1516h2_8daaf6152771_e5627efa2ab1"
GOT=$(curl -s https://kittens.sh/api/ja4)
if [ "$GOT" != "$EXPECTED" ]; then
echo "fingerprint drifted: $GOT" >&2
exit 1
fiSee what changed
/api/ja4/raw returns JA4 with both hashed halves expanded, so a diff points at the cipher or extension that moved instead of just telling you the hash is different.
diff <(curl -s https://kittens.sh/api/ja4/raw) expected.txtWhat the fingerprints are
JA4 reads t13d1516h2_8daaf6152771_e5627efa2ab1: TLS over TCP, TLS 1.3, SNI present, 15 ciphers, 16 extensions, ALPN h2. Then a hash of the sorted cipher list, and a hash of the sorted extension list plus signature algorithms. Sorting is what lets it survive the extension shuffling that broke JA3.
JA3 is the older MD5 over unsorted fields. Kept because plenty of tooling still expects it, but Chrome randomises extension order, so it is no longer stable.
Akamai fingerprints HTTP/2 rather than TLS: SETTINGS values in the order sent, the connection WINDOW_UPDATE, PRIORITY frames, and pseudo-header order.
Licensing
JA3 and JA4 are both BSD-3-Clause. The wider JA4+ suite (JA4H, JA4S and the rest) is FoxIO License 1.1, which does not permit monetisation without an OEM licence, so none of it is implemented here.