Endpointy
Wszystko wisi pod https://api.password.es. Dwa odpowiadają, a jeden jeszcze nie — i ten mówi to we własnej odpowiedzi.
- POST/v1/generate Tworzy jedno lub kilka haseł i zwraca ich analizę.
- GET/openapi.json Opis API w formacie OpenAPI 3.1.
-
POST/mcp
Serwer MCP dla asystentów. Jedno narzędzie:
generate_password. - POST/v1/check 501 Jeszcze nie istnieje. Błąd mówi, czego brakuje i gdzie iść w międzyczasie.
Tworzenie hasła
Ciało jest opcjonalne: bez niego wychodzi 16 znaków ze wszystkimi czterema rodzajami. Z nim — to, o co poprosisz.
curl -X POST https://api.password.es/v1/generate \
-H 'content-type: application/json' \
-d '{"length":20,"exclude_ambiguous":true}'
{
"passwords": [
"P#f.aK+w4pcsx]}Gx;*>"
],
"analysis": {
"length": 20,
"pool": 83,
"bits": 127.50078862693852,
"log10_guesses": 38.08053185185735,
"crack_time_log10_seconds": 26.08053185185735,
"crack_time": {
"value": "3.8 × 10¹⁸",
"unit": "years"
},
"level": 4,
"level_scale": "time",
"ceiling": false
},
"notice": "Generated on someone else's machine, which is a security antipattern even though we store nothing. For a password you will actually use, the generator at https://password.es/en/ runs entirely in your browser and sends nothing.",
"_meta": {
"plan": "anonymous",
"lang": {
"messages": "en",
"links": "en"
},
"limits": {
"burst": {
"limit": 60,
"window_seconds": 60
}
},
"quota": {
"limit": null,
"remaining": null,
"reset": "2026-09-01T00:00:00.000Z"
},
"docs": "https://password.es/api/"
}
}
Parametry
Wszystkie opcjonalne. Tabela jest po angielsku i identyczna we wszystkich osiemnastu językach, celowo: kto wpina API, wpisuje nazwy pól dokładnie tak, jak się je pisze, a osiemnaście tłumaczeń exclude_ambiguous byłoby długiem, nie zakresem.
| Field | Type | Default | Notes |
|---|---|---|---|
| length | integer 4–64 | 16 | How many characters. The same range as the generator on this site. |
| count | integer 1–20 | 1 | How many passwords to return. passwords is always an array, including with count 1. |
| lower | boolean | true | Include a–z (26 characters). |
| upper | boolean | true | Include A–Z (26 characters). |
| digits | boolean | true | Include 0–9 (10 characters). |
| symbols | boolean | true | Include ~!@#$%^&*()_+-=[]{};:,./<>? — the same 27 as the slider on the home page, no more. |
| exclude_ambiguous | boolean | false | Drops 0 O 1 I l | o. Six of them in practice, not seven: | is not in the symbol set to begin with. That is why pool reads 83 above instead of 89. |
| no_repeats | boolean | false | Avoids adjacent repeated characters. Not an absolute guarantee: it retries ten times, exactly as the web generator does. At length 64 that lets a repeat through about 0.1% of the time. |
| lang | string | en | Which language to answer in. One of the site's eighteen. Also accepted as ?lang= in the URL, which wins over this field; without either, Accept-Language is read. An unknown value is not an error — it falls back to English. See the section below: messages exist in English and Spanish, links in all eighteen. |
Co znaczy każda liczba
Ta sama zasada: referencja po angielsku, wyjaśnienie obok. Żadna z tych liczb nie jest nowa — wszystkie wychodzą z tego samego silnika, który rysuje wskaźnik na stronie głównej.
| Field | Notes |
|---|---|
| passwords | An array of strings, always — including with count 1. |
| analysis.length | How many characters came back. |
| analysis.pool | The size of the alphabet the password was drawn from. |
| analysis.bits | H = L·log2(N), the same formula as the home page. With no_repeats it becomes log2(N)+(L-1)·log2(N-1). |
| analysis.log10_guesses | The expected work, as a base-10 logarithm: half the keyspace. |
| analysis.crack_time_log10_seconds | At 1012 guesses/s, offline, fast hash. The same attack model as the rest of the site. |
| analysis.crack_time | The same figure in words. unit follows the answer language: years, años… |
| analysis.level | 0–4. The same scale as the checker, ever since the site unified the two it used to have. |
| analysis.level_scale | "time". Says where the level came from, so that a future divergence is visible instead of having to be inferred by comparing numbers. |
| analysis.ceiling | false: the server generated the password, so the figure is exact and not a ceiling. The same flag the checker uses. |
| notice | The antipattern warning, in every single response. |
| _meta.plan | "anonymous". The only lane there is; the others arrive with accounts. |
| _meta.lang | { "messages", "links" } — which language each half actually came back in. They can differ, and that is why the API says so instead of leaving you to guess. |
| _meta.limits.burst | The rate limit actually enforced: limit requests per window_seconds. |
| _meta.quota | The reserved daily-quota slot. limit and remaining are null because nobody counts daily requests yet. |
| _meta.docs | A link back to the documentation. |
Język odpowiedzi
Domyślnie odpowiada po angielsku, bo tego oczekuje ktoś, kto wpina API bez słowa. Zmienia się to na trzy sposoby, a przy rozbieżności wygrywa pierwszy: ?lang= w URL, "lang" w ciele i nagłówek Accept-Language.
Jest tu asymetria, o której warto wiedzieć: komunikaty istnieją po angielsku i hiszpańsku; odnośniki — we wszystkich osiemnastu językach serwisu. Poproszenie o niemiecki daje niemieckie odnośniki i komunikaty wciąż po angielsku.
curl -X POST 'https://api.password.es/v1/generate?lang=de' \
-H 'content-type: application/json' \
-d '{"length":20}'
"_meta": {
"lang": { "messages": "en", "links": "de" }
}
Nie trzeba zgadywać: każda odpowiedź deklaruje w _meta.lang, co zastosowano do której połowy. A język, którego nie ma, nie jest błędem — spada do angielskiego, i _meta.lang to mówi.
Sprawdzanie hasła: jeszcze nie
/v1/check zwraca 501. To nie błąd ani przeoczenie: tak ma być, a odpowiedź tłumaczy, czego brakuje i gdzie iść w międzyczasie. checker_url wskazuje na sprawdzanie na stronie w języku, o który poprosiłeś.
curl -X POST https://api.password.es/v1/check \
-H 'content-type: application/json' \
-d '{"password":"x"}'
{
"error": "not_implemented",
"message": "/v1/check does not exist yet. Returning the same numbers as the password.es checker requires its very same pattern engine, and that costs between 11 ms and 3.6 s of CPU per request depending on the input: it is waiting on a plan cap decision and on a length cap. Meanwhile the web checker does exactly this in your browser, sending nothing: https://password.es/en/checker/",
"checker_url": "https://password.es/en/checker/",
"docs": "https://password.es/api/",
"_meta": { "…": "igual que arriba" }
}
Błędy
Wszystkie mają ten sam kształt: krótki error jako kod, message zwykłym językiem —to właśnie czyta asystent AI swojemu użytkownikowi—, czasem field, które go wywołało, docs i to samo _meta co zawsze.
| HTTP | error | Notes |
|---|---|---|
| 400 | invalid_length | length outside 4–64. field names it. |
| 400 | invalid_count | count outside 1–20. field names it. |
| 400 | empty_alphabet | All four character types turned off, so there is no alphabet to draw from. No field: it is the combination, not one parameter. |
| 400 | unknown_parameter | A parameter this endpoint does not accept. field gives the offending name. |
| 429 | rate_limited | Over 60 requests in a minute. Carries Retry-After and RateLimit-* headers, and limit / window_seconds in the body. |
| 501 | not_implemented | Only from /v1/check. Carries checker_url, pointing at the web checker in the answer language. |
Limity
Jeden, i to ten, który naprawdę jest egzekwowany: 60 żądań na minutę na adres IP. Nie trzeba wierzyć tej stronie: liczba podróżuje w każdej odpowiedzi, w _meta.limits.burst.
Po przekroczeniu odpowiedzią jest 429 z Retry-After i nagłówkami RateLimit-* oraz komunikat zwykłym językiem, co robić. Nie ma w nim odnośnika do rejestracji ani do cennika, bo nie ma rejestracji ani cennika.
W _meta zobaczysz też blok quota z obiema wartościami na null. Tak jest celowo: to miejsce zarezerwowane na czas, gdy pojawią się konta, i jest puste, bo dziś nikt nie liczy żądań na dobę. Limit ogłoszony i nieegzekwowany jest gorszy niż nieogłoszenie żadnego.
Serwer MCP
MCP to protokół, którym asystenci tacy jak Claude czy ChatGPT korzystają z zewnętrznych narzędzi. Podłącz ten adres do swojego asystenta, a będzie generował hasła z dokładnie tymi liczbami, zamiast je wymyślać. Bez rejestracji i bez klucza, z tym samym limitem 60 żądań na minutę.
To serwer bezstanowy, i warto o tym wiedzieć, jeśli przychodzisz z innych serwerów MCP: na POST odpowiada JSON-em i nie otwiera strumienia, Mcp-Session-Id nie jest wysyłany ani oczekiwany, powiadomienie dostaje 202 bez treści, a GET zwraca 405. Specyfikacja 2025-06-18 wprost na to pozwala.
curl -X POST https://api.password.es/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"generate_password",
"arguments":{"length":20,"lang":"es"}}}'
Publikuje jedno narzędzie, generate_password, z tymi samymi parametrami co tabela powyżej plus lang. Nie ma check_password_strength i nie będzie, dopóki nie istnieje /v1/check: narzędzie, które zawsze zwraca błąd, nie jest narzędziem — to złamana obietnica w katalogu asystenta.
Ostrzeżenie, że to antywzorzec, podróżuje w opisie narzędzia i w każdym wyniku. To celowe: to właśnie asystent odczyta osobie, która poprosiła o hasło.
Dokumentacja, którą czytają maszyny
Poza tą stroną istnieje opis w OpenAPI 3.1, i ten jest opublikowany: api.password.es/openapi.json. To właśnie czyta generator klientów, edytor z podpowiadaniem albo agent, który chce się dowiedzieć, jakie pola istnieją, bez pytania kogokolwiek. Opisuje te same parametry co tabela powyżej, kody błędów i powód pustego quota.