De API van password.es antwoordt: zo gebruik je hem

Gepubliceerd op door David Carrero

Deze blog herhaalt al maanden hetzelfde: je wachtwoord zou je browser niet moeten verlaten. De generator haalt zijn toeval uit crypto.getRandomValues() op je eigen machine, en de wachtwoord-checker analyseert wat je typt zonder het ergens naartoe te sturen.

En vandaag publiceren we een API die wachtwoorden op een server maakt.

De tegenstrijdigheid is overduidelijk en we gaan hem niet wegpoetsen: elk antwoord van de API draagt een waarschuwing die precies dat zegt. Maar er zijn plekken waar geen browser is — een script dat honderd accounts aanmaakt, een server die een tijdelijke credential maakt, een assistent aan wie iemand een wachtwoord vraagt en die er een verzint — en daar is het alternatief niet “de browser”: het is een slecht gekozen random() of een met de hand getypte reeks. Daarvoor is dit.

De hele aanroep

Zonder sleutel, zonder registratie, zonder headers. Dit is alles:

curl -X POST https://api.password.es/v1/generate

Hij geeft een wachtwoord van 16 tekens terug en de analyse ervan. Uitgevoerd terwijl deze alinea werd geschreven: x*7$9,BUy9PPvOI?, getrokken uit een alfabet van 89 tekens, 103,6 bits entropie en een kraaktijd van 2,5 × 10¹¹ jaar. Niveau 4 van 4.

Geen van die cijfers is nieuw. Ze komen uit dezelfde motor die de meter op de startpagina tekent: dezelfde bits-formule, hetzelfde aanvalsmodel — 10¹² pogingen per seconde, offline, snelle hash — en dezelfde niveauschaal als de checker. Als de site en de API verschillende cijfers gaven voor hetzelfde wachtwoord, zou een van beide liegen.

Wat er terugkomt, veld voor veld

Het antwoord bevat passwords — altijd een array, ook als je er één vraagt — en een blok analysis met length, pool, bits, log10_guesses, crack_time_log10_seconds, crack_time, level, level_scale en ceiling.

Twee daarvan verdienen een alinea. level_scale zegt waar het niveau vandaan komt (vandaag altijd "time"), zodat een toekomstig verschil tussen site en API in het veld zichtbaar wordt in plaats van te moeten worden afgeleid uit het vergelijken van cijfers. En ceiling zegt of het cijfer exact is of een bovengrens: bij generate staat er altijd false, want de server heeft het wachtwoord gemaakt en kent zijn alfabet; in de checker van de site kan dat niet altijd, want daar breng jij het wachtwoord mee.

Daarna komt notice, de waarschuwing, en een blok _meta met het plan, de toegepaste taal, de limieten en een link terug naar de documentatie.

De opties

Allemaal optioneel, met Engelse namen omdat wie een API integreert de veldnamen letterlijk overtypt: length (4–64, standaard 16), count (1–20), lower, upper, digits, symbols, exclude_ambiguous, no_repeats en lang.

curl -X POST https://api.password.es/v1/generate -H 'content-type: application/json' -d '{"length":20,"exclude_ambiguous":true}'

Twee details die de documentatie noemt en die je maar beter kunt weten voordat je integreert. exclude_ambiguous haalt zes tekens weg, geen zeven: het schrapt 0 O 1 I l | o, maar de verticale streep zat sowieso niet in de symbolenset, dus het alfabet zakt van 89 naar 83 en niet naar 82. Daarom leest pool in het antwoord 83. En no_repeats is geen absolute garantie: het probeert het tien keer opnieuw, precies zoals de generator op de site, en bij 64 tekens glipt er in ongeveer 0,1 % van de gevallen een aangrenzende herhaling doorheen. Dat zeggen is nuttiger dan het tegendeel beloven.

De taal

Standaard antwoordt hij in het Engels, wat verwacht wordt door wie integreert zonder iets te zeggen. Je verandert het met ?lang= in de URL, met "lang" in de body of met de header Accept-Language, en als ze elkaar tegenspreken wint de eerste. Een onbekende taal is geen fout: hij valt terug op het Engels.

Hier zit een asymmetrie die je niet kunt raden, en daarom verklaart de API hem: de berichten bestaan in het Engels en Spaans; de links in alle achttien talen van de site. Duits vragen geeft je Duitse links en berichten nog steeds in het Engels. Je hoeft niets aan te nemen: _meta.lang bevat { "messages", "links" } met wat op elke helft is toegepast.

De MCP-server

MCP is het protocol waarmee een assistent — Claude, ChatGPT — externe tools gebruikt. https://api.password.es/mcp is een MCP-server zonder registratie en zonder sleutel, en publiceert één tool: generate_password, met dezelfde parameters als hierboven.

Dit is het deel dat ons het meest interesseert, en niet om technische redenen: een assistent die nergens een wachtwoord vandaan kan halen, verzint er een, en wat daaruit komt is niet willekeurig, het is wat het model wachtwoordachtig vindt. Met de server aangesloten stopt hij met improviseren en geeft hij er een terug die met crypto.getRandomValues() is gemaakt, met analyse en met de waarschuwing in de toolbeschrijving en in elk resultaat. Die waarschuwing is precies wat de assistent uiteindelijk voorleest aan wie om het wachtwoord vroeg, en daarom staat hij er.

Kom je van andere MCP-servers, dan één ding: deze is stateless. De POST wordt met JSON beantwoord en opent geen stream, Mcp-Session-Id wordt niet uitgegeven en niet verwacht, een notificatie krijgt 202 zonder body, en GET geeft 405. De specificatie 2025-06-18 staat dat expliciet toe, maar wie sessies en SSE verwacht, debugt blind als niemand het vertelt.

Wat hij niet doet

/v1/check geeft 501. Dat is geen vergissing en geen half afgemaakt endpoint: het is opzet, en het antwoord zelf legt uit waarom. Dezelfde cijfers teruggeven als de checker vraagt om precies dezelfde patroonmotor die op de site draait, en die kost tussen 11 ms en 3,6 s CPU per verzoek, afhankelijk van wat je stuurt. Bij een dienst zonder registratie en zonder sleutel is die bandbreedte een productbeslissing — waar de lengtelimiet komt te liggen — die nog niet genomen is. Ondertussen draagt de 501 een checker_url die naar de checker van de site wijst in de gevraagde taal, die precies dit doet en niets verstuurt.

Accounts bestaan evenmin, en sleutels en abonnementen ook niet. En omdat ze niet bestaan, staat er in de hele API geen enkele link naar een registratie: zelfs niet in de fout bij overschreden limiet, waar iedereen er een zet.

De limieten

Eén, en het is degene die echt wordt afgedwongen: 60 verzoeken per minuut per IP. Je hoeft deze pagina niet te geloven: het getal reist mee in elk antwoord, in _meta.limits.burst. Ga je eroverheen, dan krijg je een 429 met Retry-After en RateLimit-*-headers.

In _meta zie je ook een blok quota met de waarden op null. Dat is de plek die gereserveerd is voor wanneer er accounts zijn, en hij blijft met opzet leeg: een aangekondigde en niet afgedwongen limiet is erger dan helemaal geen limiet, want wie integreert houdt zich eraan en programmeert tegen een getal dat niemand telt.

En toch: denk er twee keer over na

Een API die wachtwoorden maakt is in de kern een antipatroon: het wachtwoord reist over het netwerk en gaat door een machine die niet van jou is. Dat wij niets bewaren verandert de vorm van het probleem niet, alleen ons deel ervan — en je weet al wat een belofte waard is die je niet kunt controleren.

Daarom leeft de waarschuwing niet alleen op deze pagina: hij reist mee in elk antwoord, in de taal die je vraagt. En daarom staat deze alinea hier en niet weggestopt achterin de documentatie. Voor een wachtwoord dat je zelf gaat gebruiken draait de generator van deze site volledig in je browser en stuurt hij niets. De API is voor het andere: wat er gebeurt zonder iemand voor een scherm.

De details staan op twee pagina’s. Wat de API is vertelt voor wie hij is, wat vandaag antwoordt en wat niet. De referentie heeft de negen parameters, alle velden stuk voor stuk uitgelegd, de foutcodes en de limieten; dat is de pagina die je naast je editor openhoudt. En wil je dat een machine het leest, dan is er openapi.json.


Bronnen: de API zelf, op 31 augustus 2026 tegen productie geverifieerd — POST /v1/generate, POST /v1/check (501), POST /mcp en GET /openapi.json · de cijfers van de analyse komen uit dezelfde motor als de generator en de checker van password.es · het aanvalsmodel is 10¹² pogingen per seconde, offline en met snelle hash, hetzelfde als op de rest van de site · de MCP-server implementeert de specificatie 2025-06-18 met stateless Streamable HTTP-transport.

Foto van Stanislav Kondratiev · Pexels

← Terug naar de blog