Changelog
This page records changes to the API and to this documentation. Entries begin 2026-09-19: anything that changed before this page existed is not listed retroactively, so treat an absent entry as “not recorded” rather than “not changed”.
How to read it
Section titled “How to read it”| Kind | Means |
|---|---|
| API | The service changed — an endpoint, a field, a limit. |
| Contract | The published OpenAPI document changed. |
| Docs | The documentation changed. The API did not move. |
| Correction | The documentation had been describing the API incorrectly. |
Correction entries are called out separately on purpose. They describe a change in what you would read, not in what the API does — so nothing in your integration breaks, but anything you built from the old description may be wrong.
2026-10-27 — Country code reference
Section titled “2026-10-27 — Country code reference”Docs · Added
- A searchable Country Codes page listing every officially assigned ISO 3166-1 alpha-2 code, linked from the endpoints that take the
countryfield. The API has always requiredcountryon phone tasks but never published the set of valid values — it only rejects an unsupported combination with422 invalid_country— so the codes themselves were the missing half of the contract.
2026-09-19 — Documentation restructured, contract published
Section titled “2026-09-19 — Documentation restructured, contract published”Docs · Added
- A Reference section: OpenAPI, Versioning & deprecation, Status & support, Security and Glossary.
- The OpenAPI 3.1 document is now published at
https://docs.zelnum.com/openapi.yaml, a byte-identical mirror of the platform’s copy, importable into Postman, Insomnia and client generators. - Client examples in Python, Node.js, PHP and Go for both detection modes, alongside the existing cURL examples. The navigation had been promising SDK examples while only cURL existed.
- Field-by-field response tables for the realtime check, bulk submission, task status and result endpoints.
- Two new build guards: the build now fails on a ragged table in any locale, and it reconciles every endpoint this documentation names against the vendored OpenAPI document in both directions — a documented endpoint that the contract lacks, and a contract path nobody documents, are both failures.
Contract · Added
- The OpenAPI document itself: 10 paths, 9 schemas, and
deprecated: trueon the legacyPOST /v1/realtime/detect.
Docs · Corrections
- Bulk
countrywas documented as required for every submission. It is required for phone products and must be omitted for email products. As written, the nine bulk email products were unsubmittable — every request would have failed validation. - The bulk volume rule was wrong. It read “1,000 to 2,000,000 numbers per request”. The real rule is the selected product’s own minimum — 500, 1,000 or 2,000 — up to a platform maximum of 2,000,000. The old figure excluded eleven products whose minimum is 500.
- The error catalogue listed four codes the API never returns. The platform’s legacy docs page names
not_found,result_not_ready,result_expiredandvalidation_failed; the implementation emits none of them, and those responses carry nocodefield at all. The table now lists the 22 codes the API actually returns, and a second table documents the codeless conditions — with a warning for anyone who ported those four branches. - Response examples were missing fields the API actually returns.
series,charged_amountandrefunded_amounton realtime responses;idempotent_replayandrecovery_pendingon bulk submissions; and ten task-progress counters includingsubmitted,prefiltered,deduped,stage,last_progress_atandfailure_message. - Rate limits named the wrong endpoint. The realtime row said
POST /v1/realtime/check/phone; the limit applies toPOST /v1/realtime/check/{series}, email included. - The response headers were described wrongly. The rate-limits page implied the full header set appears on every response. It does not: the realtime bucket reports only
X-RateLimit-Remainingon success and neverX-RateLimit-Limit, andX-RateLimit-Resetappears only on a general or detection429. The page now says which header appears where. - The key model was described wrongly. The security page claimed an account holds one key at a time. An account can hold several active keys, and the Console creates, regenerates and deletes them individually — so a staged rotation with no downtime is possible, and regenerating in place is the variant that has no overlap.
GET /v1/api-keyreports the newest active key rather than an inventory, and itslast_usedis written at most once every five minutes. - IDs versus slugs were described wrongly. The glossary said a mixed-up ID or slug is rejected as
product_mode_not_supported. On the realtime endpointsproduct_idsis validated as an array of integers, so a slug is rejected earlier as400 invalid_realtime_request;422 product_mode_not_supportedis the bulk endpoint’s answer to a realtime product. - The response-format page contradicted the balance page on currency. One said “the account’s billing currency”, the other USD. Both now say USD, which is what the contract and the balance endpoint say.
- A pricing unit was stated without a source. Bulk was described as “priced per 10,000 checks”. The catalog exposes no pricing, so the claim was removed rather than kept.
- Two corrections had only reached the hand-written pages. The currency statement and the “per 10,000 checks” claim were fixed on the English and Simplified Chinese pages, but the string table that generates the other seven locales still carried them, so seven locales kept publishing both. They are corrected in the table too.
/examples/was a different page in every locale. The English page was a short navigation hub while the other eight rendered a five-step bulk walkthrough. English now matches the walkthrough, so all nine locales carry the same content.
Docs · Fixes
- Parameter tables silently lost their description column in all seven generated locales. A shared helper emitted a three-column header for four-cell rows, and GFM drops every cell past the header’s width — so in Spanish, for instance, the “Required” value rendered under the “Description” heading and the description itself disappeared, on three pages in seven locales. The helper now takes an explicit column shape and fails the build on a ragged row, and the dist-stage guard independently re-checks every rendered table in every locale.
- Generated locales rendered the product table’s column headers, its note and its sync line in English, and the note’s backticks reached the browser as literal characters. Component copy now comes from the translated string table, and a missing translation fails the build instead of falling back.
Subscribing to changes
Section titled “Subscribing to changes”There is no feed yet. Until there is, the reliable signal is the contract:
npm run openapi:checkin a scheduled job — it exits non-zero the moment the published document differs from the copy you vendored. See OpenAPI for the two URLs.