Gli endpoint
Tutto pende da https://api.password.es. Due rispondono e uno non ancora — e quello lo dice nella propria risposta.
- POST/v1/generate Genera una o più password e ne restituisce l'analisi.
- GET/openapi.json La descrizione dell'API, in OpenAPI 3.1.
-
POST/mcp
Server MCP per assistenti. Uno strumento:
generate_password. - POST/v1/check 501 Non esiste ancora. L'errore dice che cosa manca e dove andare nel frattempo.
Generare una password
Il corpo è facoltativo: senza, escono 16 caratteri con tutti e quattro i tipi attivi. Con, quello che chiedi.
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/"
}
}
I parametri
Tutti facoltativi. La tabella è in inglese ed è identica in tutte e diciotto le lingue, di proposito: chi integra un'API scrive i nomi dei campi esattamente come sono, e diciotto traduzioni di exclude_ambiguous sarebbero debito, non portata.
| 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. |
Che cosa significa ogni numero
Stessa idea: il riferimento in inglese, la spiegazione accanto. Nessuno di questi numeri è nuovo — escono tutti dallo stesso motore che disegna l'indicatore in home page.
| 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. |
La lingua della risposta
Per impostazione predefinita risponde in inglese, che è ciò che si aspetta chi integra senza dire nulla. Si cambia in tre modi, e se sono in disaccordo vince il primo: ?lang= nell'URL, "lang" nel corpo e l'intestazione Accept-Language.
Qui c'è un'asimmetria che conviene conoscere: i messaggi esistono in inglese e spagnolo; i link, in tutte e diciotto le lingue del sito. Chiedere il tedesco dà link in tedesco e messaggi ancora in inglese.
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" }
}
Non serve indovinare: ogni risposta dichiara in _meta.lang che cosa è stato applicato a ciascuna metà. E una lingua che non esiste non è un errore — ricade sull'inglese, e _meta.lang lo dice.
Controllare una password: non ancora
/v1/check restituisce 501. Non è un bug né una dimenticanza: è voluto, e la risposta spiega che cosa manca e dove andare nel frattempo. checker_url punta al controllo del sito nella lingua che hai chiesto.
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" }
}
Gli errori
Hanno tutti la stessa forma: un error breve per il codice, un message in chiaro —è quello che un assistente di IA legge al suo utente—, a volte il field che l'ha causato, un docs e lo stesso _meta di sempre.
| 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. |
I limiti
Uno solo, ed è quello che viene davvero applicato: 60 richieste al minuto per IP. Non serve credere a questa pagina: il numero viaggia in ogni risposta, dentro _meta.limits.burst.
Superandolo, la risposta è un 429 con Retry-After e intestazioni RateLimit-*, più un messaggio in chiaro che dice che cosa fare. Non contiene link a registrazione o prezzi, perché non ci sono né registrazione né prezzi.
In _meta c'è anche un blocco quota con entrambi i valori a null. È voluto: è lo spazio riservato a quando esisteranno gli account, ed è vuoto perché oggi nessuno conta le richieste al giorno. Un limite annunciato e non fatto rispettare è peggio che non annunciarne nessuno.
Il server MCP
MCP è il protocollo con cui assistenti come Claude o ChatGPT usano strumenti esterni. Collega questo indirizzo al tuo assistente e genererà password con questi stessi numeri invece di inventarle. Senza registrazione e senza chiave, con lo stesso limite di 60 richieste al minuto.
È un server senza stato, ed è bene saperlo se vieni da altri server MCP: il POST riceve una risposta JSON senza aprire alcuno stream, Mcp-Session-Id non viene emesso né atteso, una notifica riceve 202 senza corpo, e il GET restituisce 405. La specifica 2025-06-18 lo consente esplicitamente.
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"}}}'
Pubblica un solo strumento, generate_password, con gli stessi parametri della tabella qui sopra più lang. Non c'è check_password_strength e non ci sarà finché /v1/check non esisterà: uno strumento che restituisce sempre un errore non è uno strumento, è una promessa rotta nel catalogo di un assistente.
L'avviso che questo è un antipattern viaggia nella descrizione dello strumento e in ogni risultato. È deliberato: è ciò che l'assistente finisce per leggere a chi ha chiesto la password.
La documentazione che leggono le macchine
Oltre a questa pagina c'è una descrizione in OpenAPI 3.1, e quella sì è pubblicata: api.password.es/openapi.json. È ciò che legge un generatore di client, un editor con autocompletamento o un agente che voglia sapere quali campi esistono senza che glielo dica nessuno. Descrive gli stessi parametri della tabella qui sopra, i codici d'errore e il perché del quota vuoto.